Merge branch 'feature/forgot-password' into 'master'
Feature/forgot password Closes #44 See merge request framasoft/mobilizon!43
This commit is contained in:
commit
3723eed1fe
|
@ -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>
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
7
js/src/types/login.model.ts
Normal file
7
js/src/types/login.model.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export interface ILogin {
|
||||||
|
user: {
|
||||||
|
id: number,
|
||||||
|
},
|
||||||
|
|
||||||
|
token: string,
|
||||||
|
}
|
7
js/src/utils/auth.ts
Normal file
7
js/src/utils/auth.ts
Normal 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);
|
||||||
|
}
|
7
js/src/utils/validators.ts
Normal file
7
js/src/utils/validators.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export function validateEmailField(value: string) {
|
||||||
|
return value.includes('@') || 'Invalid e-mail.';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateRequiredField(value: any) {
|
||||||
|
return !!value || 'Required.';
|
||||||
|
}
|
|
@ -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}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue