Merge branch 'feature/forgot-password' into 'master'

Feature/forgot password

Closes #44

See merge request framasoft/mobilizon!43
This commit is contained in:
Thomas Citharel 2019-01-14 10:43:18 +01:00
commit 3723eed1fe
10 changed files with 124 additions and 70 deletions

View File

@ -62,9 +62,11 @@
import Gravatar from 'vue-gravatar'; import Gravatar from 'vue-gravatar';
import RegisterAvatar from './RegisterAvatar.vue'; import RegisterAvatar from './RegisterAvatar.vue';
import { AUTH_TOKEN, AUTH_USER_ACTOR, AUTH_USER_ID } from '@/constants';
import { Component, Prop, Vue } from 'vue-property-decorator'; import { Component, Prop, Vue } from 'vue-property-decorator';
import { LOGIN } from '@/graphql/auth'; import { LOGIN } from '@/graphql/auth';
import { validateEmailField, validateRequiredField } from '@/utils/validators';
import { saveUserData } from '@/utils/auth';
import { ILogin } from '@/types/login.model'
@Component({ @Component({
components: { components: {
@ -91,8 +93,8 @@
}, },
}; };
rules = { rules = {
required: value => !!value || 'Required.', required: validateRequiredField,
email: (value) => value.includes('@') || 'Invalid e-mail.', email: validateEmailField
}; };
user: any; user: any;
@ -109,9 +111,10 @@
async loginAction(e: Event) { async loginAction(e: Event) {
e.preventDefault(); e.preventDefault();
this.error.show = false;
try { try {
const result = await this.$apollo.mutate({ const result = await this.$apollo.mutate<{ login: ILogin }>({
mutation: LOGIN, mutation: LOGIN,
variables: { variables: {
email: this.credentials.email, email: this.credentials.email,
@ -119,7 +122,7 @@
}, },
}); });
this.saveUserData(result.data); saveUserData(result.data.login);
this.$router.push({ name: 'Home' }); this.$router.push({ name: 'Home' });
} catch (err) { } catch (err) {
console.error(err); console.error(err);
@ -131,10 +134,5 @@
validEmail() { validEmail() {
return this.rules.email(this.credentials.email) === true ? 'v-gravatar' : 'avatar'; return this.rules.email(this.credentials.email) === true ? 'v-gravatar' : 'avatar';
} }
saveUserData({ login: login }) {
localStorage.setItem(AUTH_USER_ID, login.user.id);
localStorage.setItem(AUTH_TOKEN, login.token);
}
} }
</script> </script>

View File

@ -39,6 +39,10 @@
<script lang="ts"> <script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'; import { Component, Prop, Vue } from 'vue-property-decorator';
import { validateRequiredField } from '@/utils/validators';
import { RESET_PASSWORD } from '@/graphql/auth';
import { saveUserData } from '@/utils/auth';
import { ILogin } from '@/types/login.model'
@Component @Component
export default class PasswordReset extends Vue { export default class PasswordReset extends Vue {
@ -66,8 +70,8 @@
}, },
}; };
rules = { rules = {
password_length: value => value.length > 6 || 'Password must be at least 6 caracters long', password_length: value => value.length > 6 || 'Password must be at least 6 characters long',
required: value => !!value || 'Required.', required: validateRequiredField,
password_equal: value => value === this.credentials.password || 'Passwords must be the same', password_equal: value => value === this.credentials.password || 'Passwords must be the same',
}; };
@ -76,32 +80,27 @@
this.credentials.password === this.credentials.password_confirmation; this.credentials.password === this.credentials.password_confirmation;
} }
resetAction(e) { async resetAction(e) {
this.resetState(); this.resetState();
this.error.show = false;
e.preventDefault(); e.preventDefault();
console.log(this.token);
// FIXME: implements fetchStory try {
// fetchStory('/users/password-reset/post', this.$store, { const result = await this.$apollo.mutate<{ resetPassword: ILogin}>({
// method: 'POST', mutation: RESET_PASSWORD,
// body: JSON.stringify({ password: this.credentials.password, token: this.token }), variables: {
// }).then((data) => { password: this.credentials.password,
// localStorage.setItem('token', data.token); token: this.token,
// localStorage.setItem('refresh_token', data.refresh_token); },
// this.$store.commit('LOGIN_USER', data.account); });
// this.$snotify.success(this.$t('registration.success.login', { username: data.account.username }));
// this.$router.push({ name: 'Home' }); saveUserData(result.data.resetPassword);
// }, (error) => { this.$router.push({ name: 'Home' });
// Promise.resolve(error).then((errormsg) => { } catch (err) {
// console.log('errormsg', errormsg); console.error(err);
// this.error.show = true; this.error.show = true;
// Object.entries(JSON.parse(errormsg).errors).forEach(([ key, val ]) => { }
// console.log('key', key);
// console.log('val', val[ 0 ]);
// this.state[ key ] = { status: false, msg: val[ 0 ] };
// console.log('state', this.state);
// });
// });
// });
} }
resetState() { resetState() {

View File

@ -32,6 +32,8 @@
<script lang="ts"> <script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'; import { Component, Prop, Vue } from 'vue-property-decorator';
import { validateEmailField, validateRequiredField } from '@/utils/validators';
import { RESEND_CONFIRMATION_EMAIL } from '@/graphql/auth';
@Component @Component
export default class ResendConfirmation extends Vue { export default class ResendConfirmation extends Vue {
@ -49,29 +51,32 @@
}, },
}; };
rules = { rules = {
required: value => !!value || 'Required.', required: validateRequiredField,
email: (value) => { email: validateEmailField,
const pattern = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return pattern.test(value) || 'Invalid e-mail.';
},
}; };
mounted() { mounted() {
this.credentials.email = this.email; this.credentials.email = this.email;
} }
resendConfirmationAction(e) { async resendConfirmationAction(e) {
e.preventDefault(); e.preventDefault();
this.error = false;
// FIXME: implement fetchStory try {
// fetchStory('/users/resend', this.$store, { method: 'POST', body: JSON.stringify(this.credentials) }).then(() => { await this.$apollo.mutate({
// this.validationSent = true; mutation: RESEND_CONFIRMATION_EMAIL,
// }).catch((err) => { variables: {
// Promise.resolve(err).then(() => { email: this.credentials.email,
// this.error = true; },
// this.validationSent = true; });
// });
// }); } catch (err) {
console.error(err);
this.error = true;
} finally {
this.validationSent = true;
}
} }
}; };
</script> </script>

View File

@ -32,6 +32,8 @@
<script lang="ts"> <script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'; import { Component, Prop, Vue } from 'vue-property-decorator';
import { validateEmailField, validateRequiredField } from '@/utils/validators';
import { SEND_RESET_PASSWORD } from '@/graphql/auth';
@Component @Component
export default class SendPasswordReset extends Vue { export default class SendPasswordReset extends Vue {
@ -46,33 +48,36 @@
email: { email: {
status: null, status: null,
msg: '', msg: '',
}, } as { status: boolean | null, msg: string },
}; };
rules = { rules = {
required: value => !!value || 'Required.', required: validateRequiredField,
email: (value) => { email: validateEmailField,
const pattern = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return pattern.test(value) || 'Invalid e-mail.';
},
}; };
mounted() { mounted() {
this.credentials.email = this.email; this.credentials.email = this.email;
} }
resendConfirmationAction(e) { async resendConfirmationAction(e) {
e.preventDefault(); e.preventDefault();
// FIXME: implement fetchStory this.error = false;
// fetchStory('/users/password-reset/send', this.$store, { method: 'POST', body: JSON.stringify(this.credentials) }).then(() => {
// this.error = false; try {
// this.validationSent = true; await this.$apollo.mutate({
// }).catch((err) => { mutation: SEND_RESET_PASSWORD,
// Promise.resolve(err).then((data) => { variables: {
// this.error = true; email: this.credentials.email,
// this.state.email = { status: false, msg: data.errors }; },
// }); });
// });
this.validationSent = true;
} catch (err) {
console.error(err);
this.error = true;
this.state.email = { status: false, msg: err.errors };
}
} }
resetState() { resetState() {

View File

@ -10,3 +10,26 @@ mutation Login($email: String!, $password: String!) {
}, },
} }
`; `;
export const SEND_RESET_PASSWORD = gql`
mutation SendResetPassword($email: String!) {
sendResetPassword(email: $email)
}
`;
export const RESET_PASSWORD = gql`
mutation ResetPassword($token: String!, $password: String!) {
resetPassword(token: $token, password: $password) {
token,
user {
id,
}
},
}
`;
export const RESEND_CONFIRMATION_EMAIL = gql`
mutation ResendConfirmationEmail($email: String!) {
resendConfirmationEmail(email: $email)
}
`;

View File

@ -0,0 +1,7 @@
export interface ILogin {
user: {
id: number,
},
token: string,
}

7
js/src/utils/auth.ts Normal file
View File

@ -0,0 +1,7 @@
import { AUTH_TOKEN, AUTH_USER_ID } from '@/constants';
import { ILogin } from '@/types/login.model';
export function saveUserData(obj: ILogin) {
localStorage.setItem(AUTH_USER_ID, `${obj.user.id}`);
localStorage.setItem(AUTH_TOKEN, obj.token);
}

View File

@ -0,0 +1,7 @@
export function validateEmailField(value: string) {
return value.includes('@') || 'Invalid e-mail.';
}
export function validateRequiredField(value: any) {
return !!value || 'Required.';
}

View File

@ -96,7 +96,7 @@ defmodule MobilizonWeb.Resolvers.User do
Send an email to reset the password from an user Send an email to reset the password from an user
""" """
def send_reset_password(_parent, %{email: email, locale: locale}, _resolution) do def send_reset_password(_parent, %{email: email, locale: locale}, _resolution) do
with {:ok, user} <- Actors.get_user_by_email(email, false), with {:ok, user} <- Actors.get_user_by_email(email, true),
{:ok, %Bamboo.Email{} = _email_html} <- {:ok, %Bamboo.Email{} = _email_html} <-
Mobilizon.Actors.Service.ResetPassword.send_password_reset_email(user, locale) do Mobilizon.Actors.Service.ResetPassword.send_password_reset_email(user, locale) do
{:ok, email} {:ok, email}

View File

@ -9,7 +9,10 @@ defmodule Mobilizon.Factory do
%Mobilizon.Actors.User{ %Mobilizon.Actors.User{
password_hash: "Jane Smith", password_hash: "Jane Smith",
email: sequence(:email, &"email-#{&1}@example.com"), email: sequence(:email, &"email-#{&1}@example.com"),
role: 0 role: 0,
confirmed_at: DateTime.utc_now(),
confirmation_sent_at: nil,
confirmation_token: nil
} }
end end