From 5339bc11432ed36dad58c5efe67218eb5e6219ac Mon Sep 17 00:00:00 2001 From: alexm Date: Wed, 9 Nov 2022 14:51:30 +0100 Subject: [PATCH] feat(recoverPassword): use loopback --- back/methods/account/recover-password.js | 35 ++---------- .../account/specs/recover-password.spec.js | 25 --------- back/models/specs/user.spec.js | 32 +++++++++++ back/models/user.js | 7 ++- e2e/helpers/selectors.js | 5 ++ e2e/paths/01-salix/04_recoverPassword.spec.js | 40 +++++++++++++ front/salix/components/app/app.html | 6 +- front/salix/components/app/app.js | 6 +- front/salix/components/login/locale/en.yml | 6 +- front/salix/components/login/locale/es.yml | 7 +++ .../components/login/recover-password.js | 10 +++- .../components/login/reset-password.html | 2 +- modules/account/front/descriptor/index.js | 12 ---- .../account/front/descriptor/index.spec.js | 11 ---- modules/account/front/routes.json | 2 +- print/core/smtp.js | 56 +++++++++++-------- 16 files changed, 149 insertions(+), 113 deletions(-) delete mode 100644 back/methods/account/specs/recover-password.spec.js create mode 100644 back/models/specs/user.spec.js create mode 100644 e2e/paths/01-salix/04_recoverPassword.spec.js diff --git a/back/methods/account/recover-password.js b/back/methods/account/recover-password.js index b48c768114..618b46946e 100644 --- a/back/methods/account/recover-password.js +++ b/back/methods/account/recover-password.js @@ -1,7 +1,5 @@ -const {Email} = require('vn-print'); - module.exports = Self => { - Self.remoteMethodCtx('recoverPassword', { + Self.remoteMethod('recoverPassword', { description: 'Send email to the user', accepts: [ { @@ -17,34 +15,13 @@ module.exports = Self => { } }); - Self.recoverPassword = async function(ctx, email) { + Self.recoverPassword = async function(email) { const models = Self.app.models; - const origin = ctx.req.headers.origin; - const ttl = 1209600; - const user = await models.Account.findOne({ - fields: ['id', 'name', 'password'], - where: { - email: email - } - }); - - if (!user) + try { + await models.user.resetPassword({email}); + } catch (e) { return; - - const token = await models.AccessToken.create({ - ttl: ttl, - userId: user.id - }); - - const url = `${origin}/#!/account/${user.id}/basic-data?access_token=${token.id}`; - const params = { - recipient: email, - url: url - }; - - const sendEmail = new Email('recover-password', params); - - return sendEmail.send(); + } }; }; diff --git a/back/methods/account/specs/recover-password.spec.js b/back/methods/account/specs/recover-password.spec.js deleted file mode 100644 index 82d0d8e59c..0000000000 --- a/back/methods/account/specs/recover-password.spec.js +++ /dev/null @@ -1,25 +0,0 @@ -const models = require('vn-loopback/server/server').models; - -describe('account recoverPassword()', () => { - const ctx = { - req: { - headers: {origin: 'http://localhost:5000'} - } - }; - ctx.req.__ = value => { - return value; - }; - - it('should update password when it passes requirements', async() => { - const user = await models.Account.findById(1107); - await models.Account.recoverPassword(ctx, user.email); - - const [result] = await models.AccessToken.find({ - where: { - userId: user.id - } - }); - - expect(result).toBeDefined(); - }); -}); diff --git a/back/models/specs/user.spec.js b/back/models/specs/user.spec.js new file mode 100644 index 0000000000..124afdc0cc --- /dev/null +++ b/back/models/specs/user.spec.js @@ -0,0 +1,32 @@ +const models = require('vn-loopback/server/server').models; +const LoopBackContext = require('loopback-context'); + +describe('account recoverPassword()', () => { + const userId = 1107; + + const activeCtx = { + accessToken: {userId: userId}, + http: { + req: { + headers: {origin: 'http://localhost'} + } + } + }; + + beforeEach(() => { + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ + active: activeCtx + }); + }); + + it('should send email with token', async() => { + const userId = 1107; + const user = await models.Account.findById(userId); + + await models.Account.recoverPassword(user.email); + + const result = await models.AccessToken.findOne({where: {userId: userId}}); + + expect(result).toBeDefined(); + }); +}); diff --git a/back/models/user.js b/back/models/user.js index 0bbd39055b..082d90500e 100644 --- a/back/models/user.js +++ b/back/models/user.js @@ -9,12 +9,15 @@ module.exports = function(Self) { const headers = httpRequest.headers; const origin = headers.origin; + const user = await Self.app.models.Account.findById(info.user.id); const params = { + recipient: info.email, + lang: user.lang, url: `${origin}/#!/login/reset-password?access_token=${info.accessToken.id}` }; - const sendEmail = new Email('recover-password', params); + const email = new Email('recover-password', params); - return sendEmail.send(); + return email.send(); }); }; diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index f0d726ed67..0ff9384849 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -29,6 +29,11 @@ export default { firstModulePinIcon: 'vn-home a:nth-child(1) vn-icon[icon="push_pin"]', firstModuleRemovePinIcon: 'vn-home a:nth-child(1) vn-icon[icon="remove_circle"]' }, + recoverPassword: { + recoverPasswordButton: 'vn-login a[ui-sref="login.recover-password"]', + email: 'vn-recover-password vn-textfield[ng-model="$ctrl.email"]', + sendEmailButton: 'vn-recover-password vn-submit', + }, accountIndex: { addAccount: 'vn-user-index button vn-icon[icon="add"]', newName: 'vn-user-create vn-textfield[ng-model="$ctrl.user.name"]', diff --git a/e2e/paths/01-salix/04_recoverPassword.spec.js b/e2e/paths/01-salix/04_recoverPassword.spec.js new file mode 100644 index 0000000000..f0dd884b22 --- /dev/null +++ b/e2e/paths/01-salix/04_recoverPassword.spec.js @@ -0,0 +1,40 @@ +import selectors from '../../helpers/selectors'; +import getBrowser from '../../helpers/puppeteer'; + +fdescribe('Login path', async() => { + let browser; + let page; + + beforeAll(async() => { + browser = await getBrowser(); + page = browser.page; + + await page.waitToClick(selectors.recoverPassword.recoverPasswordButton); + await page.waitForState('login.recover-password'); + }); + + afterAll(async() => { + await browser.close(); + }); + + it('should not throw error if not exist user', async() => { + await page.write(selectors.recoverPassword.email, 'fakeEmail@mydomain.com'); + await page.waitToClick(selectors.recoverPassword.sendEmailButton); + + const message = await page.waitForSnackbar(); + + expect(message.text).toContain('Notification sent!'); + }); + + it('should send email', async() => { + await page.waitForState('login'); + await page.waitToClick(selectors.recoverPassword.recoverPasswordButton); + + await page.write(selectors.recoverPassword.email, 'BruceWayne@mydomain.com'); + await page.waitToClick(selectors.recoverPassword.sendEmailButton); + const message = await page.waitForSnackbar(); + await page.waitForState('login'); + + expect(message.text).toContain('Notification sent!'); + }); +}); diff --git a/front/salix/components/app/app.html b/front/salix/components/app/app.html index 420a6895c8..5ad284857e 100644 --- a/front/salix/components/app/app.html +++ b/front/salix/components/app/app.html @@ -3,15 +3,15 @@ + ng-if="$ctrl.isLogin"> + ng-if="$ctrl.isRecover"> + ng-if="$ctrl.isReset"> diff --git a/front/salix/components/app/app.js b/front/salix/components/app/app.js index 50aee2f9fa..dbb0d5af5d 100644 --- a/front/salix/components/app/app.js +++ b/front/salix/components/app/app.js @@ -15,7 +15,11 @@ export default class App extends Component { get showLayout() { let state = this.$state.current.name; - return state && state != 'login' && !this.isRecover && !this.isReset; + return state && !this.isLogin && !this.isRecover && !this.isReset; + } + + get isLogin() { + return this.$state.current.name == 'login'; } get isRecover() { diff --git a/front/salix/components/login/locale/en.yml b/front/salix/components/login/locale/en.yml index c59a6dd8e6..1ddd454b70 100644 --- a/front/salix/components/login/locale/en.yml +++ b/front/salix/components/login/locale/en.yml @@ -1,4 +1,8 @@ User: User Password: Password Do not close session: Do not close session -Enter: Enter \ No newline at end of file +Enter: Enter +Password requirements: > + The password must have at least {{ length }} length characters, + {{nAlpha}} alphabetic characters, {{nUpper}} capital letters, {{nDigits}} + digits and {{nPunct}} symbols (Ex: $%&.) diff --git a/front/salix/components/login/locale/es.yml b/front/salix/components/login/locale/es.yml index afb5385131..e3a5815c1c 100644 --- a/front/salix/components/login/locale/es.yml +++ b/front/salix/components/login/locale/es.yml @@ -7,3 +7,10 @@ I do not remember my password: No recuerdo mi contraseña Recover password: Recuperar contraseña We will sent you an email to recover your password: Te enviaremos un correo para restablecer tu contraseña Notification sent!: ¡Notificación enviada! +Reset password: Restrablecer contraseña +New password: Nueva contraseña +Repeat password: Repetir contraseña +Password requirements: > + La contraseña debe tener al menos {{ length }} caracteres de longitud, + {{nAlpha}} caracteres alfabéticos, {{nUpper}} letras mayúsculas, {{nDigits}} + dígitos y {{nPunct}} símbolos (Ej: $%&.) diff --git a/front/salix/components/login/recover-password.js b/front/salix/components/login/recover-password.js index 5171c911d8..fa9bfc4599 100644 --- a/front/salix/components/login/recover-password.js +++ b/front/salix/components/login/recover-password.js @@ -13,15 +13,19 @@ export default class Controller { }); } + goToLogin() { + this.vnApp.showSuccess(this.$translate.instant('Notification sent!')); + this.$state.go('login'); + } + submit() { const params = { email: this.email }; - this.$http.post('users/reset', params) + this.$http.post('Accounts/recoverPassword', params) .then(() => { - this.vnApp.showSuccess(this.$translate.instant('Notification sent!')); - this.$state.go('login'); + this.goToLogin(); }); } } diff --git a/front/salix/components/login/reset-password.html b/front/salix/components/login/reset-password.html index 431af16fdf..eaf58494fb 100644 --- a/front/salix/components/login/reset-password.html +++ b/front/salix/components/login/reset-password.html @@ -14,7 +14,7 @@ type="password">