diff --git a/src/components/ui/VnOutForm.vue b/src/components/ui/VnOutForm.vue
new file mode 100644
index 000000000..e7ad441d0
--- /dev/null
+++ b/src/components/ui/VnOutForm.vue
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+ {{ title }}
+
+
+
+
+
+
+
+
+
diff --git a/src/i18n/locale/en.yml b/src/i18n/locale/en.yml
index 12680d0cb..ee53da80c 100644
--- a/src/i18n/locale/en.yml
+++ b/src/i18n/locale/en.yml
@@ -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
diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml
index 900de6d07..86963f3dc 100644
--- a/src/i18n/locale/es.yml
+++ b/src/i18n/locale/es.yml
@@ -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
diff --git a/src/pages/Login/LoginMain.vue b/src/pages/Login/LoginMain.vue
index 4eb21f573..c17201969 100644
--- a/src/pages/Login/LoginMain.vue
+++ b/src/pages/Login/LoginMain.vue
@@ -72,7 +72,8 @@ async function onSubmit() {
:rules="[(val) => (val && val.length > 0) || t('login.fieldRequired')]"
class="red"
/>
-
+
+
+
+ {{ t('I do not remember my password') }}
+
-
-
+
+es:
+ I do not remember my password: No recuerdo mi contraseña
+
diff --git a/src/pages/Login/RecoverPassword.vue b/src/pages/Login/RecoverPassword.vue
new file mode 100644
index 000000000..5e0fd367a
--- /dev/null
+++ b/src/pages/Login/RecoverPassword.vue
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/pages/Login/ResetPassword.vue b/src/pages/Login/ResetPassword.vue
new file mode 100644
index 000000000..eff718e97
--- /dev/null
+++ b/src/pages/Login/ResetPassword.vue
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/pages/Login/TwoFactor.vue b/src/pages/Login/TwoFactor.vue
index 31b4ccc79..dd404094f 100644
--- a/src/pages/Login/TwoFactor.vue
+++ b/src/pages/Login/TwoFactor.vue
@@ -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,24 +39,22 @@ async function onSubmit() {
}
-
-
-
-
{{ t('twoFactor.insert') }}
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
+
+
-
diff --git a/src/router/index.js b/src/router/index.js
index faa3ab5d4..686da2dde 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -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 } });
}
diff --git a/src/router/routes.js b/src/router/routes.js
index 805eefb8c..cced308b5 100644
--- a/src/router/routes.js
+++ b/src/router/routes.js
@@ -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'),
+ },
],
},
{
diff --git a/test/cypress/integration/login.spec.js b/test/cypress/integration/outLogin/login.spec.js
similarity index 100%
rename from test/cypress/integration/login.spec.js
rename to test/cypress/integration/outLogin/login.spec.js
diff --git a/test/cypress/integration/logout.spec.js b/test/cypress/integration/outLogin/logout.spec.js
similarity index 100%
rename from test/cypress/integration/logout.spec.js
rename to test/cypress/integration/outLogin/logout.spec.js
diff --git a/test/cypress/integration/outLogin/recoverPassword.spec.js b/test/cypress/integration/outLogin/recoverPassword.spec.js
new file mode 100755
index 000000000..eec81b661
--- /dev/null
+++ b/test/cypress/integration/outLogin/recoverPassword.spec.js
@@ -0,0 +1,54 @@
+///
+describe('Recover Password', () => {
+ const username = 'trainee';
+ beforeEach(() => {
+ cy.visit('/#/login');
+ cy.get('#switchLanguage').click();
+ cy.get('.q-menu > :nth-child(1) > .q-item').click();
+ });
+
+ it('should go to recover password section and send notification', () => {
+ cy.get('input[aria-label="Username"]').type(username);
+ cy.get(`a[href="#/recoverPassword?user=${username}"]`).click();
+
+ cy.waitForElement('input[aria-label="User or recovery email"]');
+ cy.get('input[aria-label="User or recovery email"]').should(
+ 'have.value',
+ username
+ );
+
+ cy.get('button[type="submit"]').click();
+ cy.get('.q-notification__message').should('have.text', 'Notification sent');
+ });
+
+ it('should change password to user', () => {
+ // Get token from mail
+ cy.request(
+ `http://localhost:3000/api/Mails?filter=%7B%22where%22%3A%20%7B%22receiver%22%3A%20%22${username}%40mydomain.com%22%7D%2C%20%22order%22%3A%20%5B%22id%20DESC%22%5D%7D&access_token=DEFAULT_TOKEN`
+ ).then((response) => {
+ const regex = /access_token=([a-zA-Z0-9]+)/;
+ const [match] = response.body[0].body.match(regex);
+
+ const resetUrl = '/#/resetPassword?' + match;
+ cy.visit(resetUrl);
+ });
+
+ // Change password
+ const newPassword = 'test.1234';
+ cy.waitForElement('input[aria-label="Password"]');
+ cy.waitForElement('input[aria-label="Repeat password"]');
+ cy.get('input[aria-label="Password"]').type(newPassword);
+ cy.get('input[aria-label="Repeat password"]').type(newPassword);
+
+ cy.get('button[type="submit"]').click();
+ cy.get('.q-notification__message').should('have.text', 'Password changed');
+
+ // Try to login successfully
+ cy.get('input[aria-label="Username"]').type(username);
+ cy.get('input[aria-label="Password"]').type(newPassword);
+ cy.get('button[type="submit"]').click();
+ cy.url().should('contain', '/dashboard');
+
+ // ❗The password cannot be returned because "nightmare" does not meet the requirements
+ });
+});
diff --git a/test/cypress/integration/outLogin/twoFactor.spec.js b/test/cypress/integration/outLogin/twoFactor.spec.js
new file mode 100755
index 000000000..4d8561f0f
--- /dev/null
+++ b/test/cypress/integration/outLogin/twoFactor.spec.js
@@ -0,0 +1,56 @@
+///
+describe('Two Factor', () => {
+ const username = 'sysadmin';
+ const userId = 66;
+ beforeEach(() => {
+ cy.visit('/#/login');
+ cy.get('#switchLanguage').click();
+ cy.get('.q-menu > :nth-child(1) > .q-item').click();
+ });
+
+ it('should enable two factor to sysadmin', () => {
+ cy.request(
+ 'PATCH',
+ `http://localhost:3000/api/VnUsers/${userId}/update-user?access_token=DEFAULT_TOKEN`,
+ { twoFactor: 'email' }
+ );
+ });
+
+ it('should fail when login with incorrect two factor', () => {
+ cy.get('input[aria-label="Username"]').type(username);
+ cy.get('input[aria-label="Password"]').type('nightmare');
+ cy.get('button[type="submit"]').click();
+ cy.get('.q-notification__message').should(
+ 'have.text',
+ 'Two-factor verification required'
+ );
+
+ cy.get('input[type="text"]').type('123456');
+ cy.get('button[type="submit"]').click();
+ cy.url().should('contain', '/twoFactor');
+ });
+
+ it('should login with correct two factor', () => {
+ cy.get('input[aria-label="Username"]').type(username);
+ cy.get('input[aria-label="Password"]').type('nightmare');
+ cy.get('button[type="submit"]').click();
+ cy.get('.q-notification__message').should(
+ 'have.text',
+ 'Two-factor verification required'
+ );
+
+ // Get code from mail
+ cy.request(
+ `http://localhost:3000/api/Mails?filter=%7B%22where%22%3A%20%7B%22receiver%22%3A%20%22${username}%40mydomain.com%22%7D%2C%20%22order%22%3A%20%5B%22id%20DESC%22%5D%7D&access_token=DEFAULT_TOKEN`
+ ).then((response) => {
+ const tempDiv = document.createElement('div');
+ tempDiv.innerHTML = response.body[0].body;
+ const codeElement = tempDiv.querySelector('.code');
+ const code = codeElement ? codeElement.textContent.trim() : null;
+
+ cy.get('input[type="text"]').type(code);
+ cy.get('button[type="submit"]').click();
+ cy.url().should('contain', '/dashboard');
+ });
+ });
+});