0
0
Fork 0

feat: add recover password and reset password

This commit is contained in:
Alex Moreno 2024-08-13 12:25:08 +02:00
parent d27821c100
commit a46198e2d0
9 changed files with 256 additions and 46 deletions

View File

@ -0,0 +1,32 @@
<script setup>
const emit = defineEmits(['submit']);
defineProps({
icon: { type: String, required: false, default: 'phonelink_lock' },
title: { type: String, required: true },
});
</script>
<template>
<QForm @submit="emit('submit')" class="q-gutter-y-md q-pa-lg formCard">
<div class="column items-center">
<QIcon v-if="icon != false" :name="icon" size="xl" color="primary" />
<h5 class="text-center q-my-md">
{{ title }}
</h5>
</div>
<slot name="default"></slot>
<div class="q-mt-lg">
<slot name="buttons"></slot>
</div>
</QForm>
</template>
<style lang="scss" scoped>
.formCard {
max-width: 350px;
min-width: 300px;
}
@media (max-width: $breakpoint-xs-max) {
.formCard {
min-width: 100%;
}
}
</style>

View File

@ -251,6 +251,9 @@ globals:
privileges: Privileges
ldap: LDAP
samba: Samba
twoFactor: Two factor
recoverPassword: Recover password
resetPassword: Reset password
created: Created
worker: Worker
now: Now
@ -288,14 +291,17 @@ twoFactor:
explanation: >-
Please, enter the verification code that we have sent to your email in the
next 5 minutes
pageTitles:
twoFactor: Two-Factor
verifyEmail:
pageTitles:
verifyEmail: Email verification
dashboard:
pageTitles:
recoverPassword:
userOrEmail: User or recovery email
explanation: >-
We will sent you an email to recover your password
resetPassword:
repeatPassword: Repeat password
passwordNotMatch: Passwords don't match
passwordChanged: Password changed
customer:
list:
phone: Phone

View File

@ -253,6 +253,9 @@ globals:
packages: Bultos
ldap: LDAP
samba: Samba
twoFactor: Doble factor
recoverPassword: Recuperar contraseña
resetPassword: Restablecer contraseña
created: Fecha creación
worker: Trabajador
now: Ahora
@ -289,14 +292,17 @@ twoFactor:
validate: Validar
insert: Introduce el código de verificación
explanation: Por favor introduce el código de verificación que te hemos enviado a tu email en los próximos 5 minutos
pageTitles:
twoFactor: Doble factor
verifyEmail:
pageTitles:
verifyEmail: Verificación de correo
dashboard:
pageTitles:
recoverPassword:
userOrEmail: Usuario o correo de recuperación
explanation: >-
Te enviaremos un correo para restablecer tu contraseña
resetPassword:
repeatPassword: Repetir contraseña
passwordNotMatch: Las contraseñas no coinciden
passwordChanged: Contraseña cambiada
customer:
list:
phone: Teléfono

View File

@ -72,7 +72,8 @@ async function onSubmit() {
:rules="[(val) => (val && val.length > 0) || t('login.fieldRequired')]"
class="red"
/>
<div>
<QToggle v-model="keepLogin" :label="t('login.keepLogin')" />
<div class="column flex-center q-mt-lg">
<QBtn
:label="t('login.submit')"
type="submit"
@ -81,11 +82,15 @@ async function onSubmit() {
rounded
unelevated
/>
<RouterLink
class="q-mt-md text-primary"
:to="`/recoverPassword?user=${username}`"
>
{{ t('I do not remember my password') }}
</RouterLink>
</div>
<QToggle v-model="keepLogin" :label="t('login.keepLogin')" />
</QForm>
</template>
<style lang="scss" scoped>
.formCard {
max-width: 350px;
@ -101,3 +106,7 @@ async function onSubmit() {
}
}
</style>
<i18n>
es:
I do not remember my password: No recuerdo mi contraseña
</i18n>

View File

@ -0,0 +1,59 @@
<script setup>
import { ref } from 'vue';
import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n';
import { useRouter, useRoute } from 'vue-router';
import axios from 'axios';
import VnInput from 'src/components/common/VnInput.vue';
import VnOutForm from 'src/components/ui/VnOutForm.vue';
const quasar = useQuasar();
const router = useRouter();
const route = useRoute();
const { t } = useI18n();
const user = ref(route.query.user);
async function onSubmit() {
try {
await axios.post('VnUsers/recoverPassword', { user: user.value, app: 'lilium' });
router.push('Login');
quasar.notify({
message: t('globals.notificationSent'),
type: 'positive',
});
} catch (e) {
quasar.notify({
message: e.response?.data?.error.message,
type: 'negative',
});
}
}
</script>
<template>
<VnOutForm @submit="onSubmit" :title="t('globals.pageTitles.recoverPassword')">
<template #default>
<VnInput
v-model="user"
:label="t('recoverPassword.userOrEmail')"
:hint="t('recoverPassword.explanation')"
autofocus
required
>
<template #prepend>
<QIcon name="contact_mail" />
</template>
</VnInput>
</template>
<template #buttons>
<QBtn
:label="t('globals.pageTitles.recoverPassword')"
type="submit"
color="primary"
class="full-width q-mt-md"
rounded
unelevated
/>
</template>
</VnOutForm>
</template>

View File

@ -0,0 +1,99 @@
<script setup>
import { ref, onMounted } from 'vue';
import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n';
import { useRouter, useRoute } from 'vue-router';
import axios from 'axios';
import VnInput from 'components/common/VnInput.vue';
import VnOutForm from 'components/ui/VnOutForm.vue';
const quasar = useQuasar();
const router = useRouter();
const route = useRoute();
const { t } = useI18n();
const newPassword = ref();
const repeatPassword = ref();
const passRequirements = ref({});
onMounted(async () => {
passRequirements.value = (await axios('UserPasswords/findOne')).data;
});
async function onSubmit() {
if (newPassword.value != repeatPassword.value)
return quasar.notify({
message: t('resetPassword.passwordNotMatch'),
type: 'negative',
});
const headers = {
Authorization: route.query.access_token,
};
try {
console.log('newPassword: ', newPassword);
await axios.post(
'VnUsers/reset-password',
{ newPassword: newPassword.value },
{ headers }
);
router.push('Login');
quasar.notify({
message: t('resetPassword.passwordChanged'),
type: 'positive',
});
} catch (e) {
quasar.notify({
message: e.response?.data?.error.message,
type: 'negative',
});
}
}
</script>
<template>
<VnOutForm @submit="onSubmit" :title="t('globals.pageTitles.resetPassword')">
<template #default>
<VnInput
type="password"
:label="t('login.password')"
v-model="newPassword"
:info="
t('passwordRequirements', {
length: passRequirements.length,
nAlpha: passRequirements.nAlpha,
nUpper: passRequirements.nUpper,
nDigits: passRequirements.nDigits,
nPunct: passRequirements.nPunct,
})
"
required
>
<template #prepend>
<QIcon name="password" />
</template>
</VnInput>
<VnInput
type="password"
:label="t('resetPassword.repeatPassword')"
v-model="repeatPassword"
required
>
<template #prepend>
<QIcon name="password" />
</template>
</VnInput>
</template>
<template #buttons>
<QBtn
:label="t('globals.pageTitles.resetPassword')"
type="submit"
color="primary"
class="full-width q-mt-md"
rounded
unelevated
/>
</template>
</VnOutForm>
</template>

View File

@ -8,6 +8,7 @@ import axios from 'axios';
import { useSession } from 'src/composables/useSession';
import { useLogin } from 'src/composables/useLogin';
import VnInput from 'src/components/common/VnInput.vue';
import VnOutForm from 'src/components/ui/VnOutForm.vue';
const quasar = useQuasar();
const session = useSession();
@ -38,11 +39,8 @@ async function onSubmit() {
}
</script>
<template>
<QForm @submit="onSubmit" class="q-gutter-y-md q-pa-lg formCard">
<div class="column items-center">
<QIcon name="phonelink_lock" size="xl" color="primary" />
<h5 class="text-center q-my-md">{{ t('twoFactor.insert') }}</h5>
</div>
<VnOutForm @submit="onSubmit" :title="t('twoFactor.insert')">
<template #default>
<VnInput
v-model="code"
:hint="t('twoFactor.explanation')"
@ -55,7 +53,8 @@ async function onSubmit() {
<QIcon name="lock" />
</template>
</VnInput>
<div class="q-mt-xl">
</template>
<template #buttons>
<QBtn
:label="t('twoFactor.validate')"
type="submit"
@ -64,18 +63,6 @@ async function onSubmit() {
rounded
unelevated
/>
</div>
</QForm>
</template>
<style lang="scss" scoped>
.formCard {
max-width: 350px;
min-width: 300px;
}
@media (max-width: $breakpoint-xs-max) {
.formCard {
min-width: 100%;
}
}
</style>
</VnOutForm>
</template>

View File

@ -46,7 +46,7 @@ export { Router };
export default route(function (/* { store, ssrContext } */) {
Router.beforeEach(async (to, from, next) => {
const { isLoggedIn } = session;
const outLayout = ['Login', 'TwoFactor', 'VerifyEmail'];
const outLayout = Router.options.routes[0].children.map((r) => r.name);
if (!isLoggedIn() && !outLayout.includes(to.name)) {
return next({ name: 'Login', query: { redirect: to.fullPath } });
}

View File

@ -46,6 +46,18 @@ const routes = [
meta: { title: 'verifyEmail' },
component: () => import('../pages/Login/VerifyEmail.vue'),
},
{
path: '/recoverPassword',
name: 'RecoverPassword',
meta: { title: 'recoverPassword' },
component: () => import('../pages/Login/RecoverPassword.vue'),
},
{
path: '/resetPassword',
name: 'ResetPassword',
meta: { title: 'resetPassword' },
component: () => import('../pages/Login/ResetPassword.vue'),
},
],
},
{