diff --git a/back/methods/account/recover-password.js b/back/methods/account/recover-password.js index 66f2fbcda..54ccc56bc 100644 --- a/back/methods/account/recover-password.js +++ b/back/methods/account/recover-password.js @@ -38,11 +38,21 @@ module.exports = Self => { userId: user.id }); + const title = $t('Recover password'); + const body = ` +

+ ${$t('Click on the following link to change your password')}: +

+ + + ${title} + `; + await Self.rawSql(`CALL vn.mail_insert(?,?,?,?)`, [ email, null, - $t('Recovery password'), - `${origin}/#!/account/${user.id}/basic-data?access_token=${token.id}` + title, + body ]); return; diff --git a/back/models/account.js b/back/models/account.js index 5ace3b858..506dc37ac 100644 --- a/back/models/account.js +++ b/back/models/account.js @@ -1,4 +1,6 @@ +/* eslint max-len: ["error", { "code": 150 }]*/ const md5 = require('md5'); +const LoopBackContext = require('loopback-context'); module.exports = Self => { require('../methods/account/login')(Self); @@ -27,6 +29,40 @@ module.exports = Self => { ctx.data.password = md5(ctx.data.password); }); + Self.observe('before save', async ctx => { + const models = Self.app.models; + const loopBackContext = LoopBackContext.getCurrentContext(); + const changes = ctx.data || ctx.instance; + if (ctx.isNewInstance || !changes.email) return; + + const userId = ctx.currentInstance.id; + const user = await models.Account.findById(userId); + if (user.email == changes.email) return; + + const httpCtx = {req: loopBackContext.active}; + const httpRequest = httpCtx.req.http.req; + const headers = httpRequest.headers; + const origin = headers.origin; + const $t = httpRequest.__; + + const title = $t('Verify email'); + const body = ` +

+ ${$t(`Click on the following link to verify this email. If you haven't requested this email, just ignore it`)}: +

+ + + ${title} + `; + + result = await Self.rawSql(`CALL vn.mail_insert(?,?,?,?)`, [ + changes.email, + null, + title, + body + ], ctx.options); + }); + Self.remoteMethod('getCurrentUserData', { description: 'Gets the current user data', accepts: [ diff --git a/back/models/account.json b/back/models/account.json index b07c982f8..b4c1ca4be 100644 --- a/back/models/account.json +++ b/back/models/account.json @@ -40,6 +40,9 @@ "email": { "type": "string" }, + "emailVerified": { + "type": "boolean" + }, "created": { "type": "date" }, diff --git a/back/models/specs/account.spec.js b/back/models/specs/account.spec.js index c52bc4378..7e4d877f1 100644 --- a/back/models/specs/account.spec.js +++ b/back/models/specs/account.spec.js @@ -1,15 +1,61 @@ -const app = require('vn-loopback/server/server'); +const models = require('vn-loopback/server/server').models; +const LoopBackContext = require('loopback-context'); describe('loopback model Account', () => { + const userId = 1105; + const activeCtx = { + accessToken: {userId: userId}, + http: { + req: { + headers: {origin: 'http://localhost'}, + [`__`]: value => { + return value; + } + } + } + }; + + beforeEach(() => { + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ + active: activeCtx + }); + }); + it('should return true if the user has the given role', async() => { - let result = await app.models.Account.hasRole(1, 'employee'); + let result = await models.Account.hasRole(1, 'employee'); expect(result).toBeTruthy(); }); it('should return false if the user doesnt have the given role', async() => { - let result = await app.models.Account.hasRole(1, 'administrator'); + let result = await models.Account.hasRole(1, 'administrator'); expect(result).toBeFalsy(); }); + + it('should send email when change email', async() => { + const tx = await models.Account.beginTransaction({}); + const newEmail = 'emailNotVerified@mydomain.com'; + + let lastEmail; + try { + const options = {transaction: tx}; + + const account = await models.Account.findById(userId, null, options); + await account.updateAttribute('email', newEmail, options); + + [lastEmail] = await models.Mail.find({ + where: { + receiver: newEmail + } + }, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + + expect(lastEmail.receiver).toEqual(newEmail); + }); }); diff --git a/db/changes/10500-motherOfGod/00-acl_recover-password.sql b/db/changes/10500-motherOfGod/00-acl_recover-password.sql deleted file mode 100644 index 65e7b7cf3..000000000 --- a/db/changes/10500-motherOfGod/00-acl_recover-password.sql +++ /dev/null @@ -1,3 +0,0 @@ -INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) - VALUES - ('Account', 'recoverPassword', 'READ', 'ALLOW', 'ROLE', 'account'); diff --git a/loopback/locale/en.json b/loopback/locale/en.json index ccf16cce0..23d999f5b 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -130,5 +130,7 @@ "Descanso diario 12h.": "Daily rest 12h.", "Fichadas impares": "Odd signs", "Descanso diario 9h.": "Daily rest 9h.", - "Descanso semanal 36h. / 72h.": "Weekly rest 36h. / 72h." + "Descanso semanal 36h. / 72h.": "Weekly rest 36h. / 72h.", + "Verify email": "Verify email", + "Click on the following link to verify this email. If you haven't requested this email, just ignore it": "Click on the following link to verify this email. If you haven't requested this email, just ignore it" } \ No newline at end of file diff --git a/loopback/locale/es.json b/loopback/locale/es.json index c85757c1f..79bb9a458 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -234,5 +234,8 @@ "Descanso semanal 36h. / 72h.": "Descanso semanal 36h. / 72h.", "Dirección incorrecta": "Dirección incorrecta", "This email does not belong to a user": "Este correo electrónico no pertenece a un usuario.", - "Recovery password": "Recuperar contraseña" + "Recover password": "Recuperar contraseña", + "Click on the following link to change your password.": "Pulsa en el siguiente link para cambiar tu contraseña.", + "Verify email": "Verificar correo", + "Click on the following link to verify this email. If you haven't requested this email, just ignore it": "Pulsa en el siguiente link para verificar este correo. Si no has pedido este correo, simplemente ignóralo." } diff --git a/modules/account/front/basic-data/index.js b/modules/account/front/basic-data/index.js index 342297e45..1d27477b7 100644 --- a/modules/account/front/basic-data/index.js +++ b/modules/account/front/basic-data/index.js @@ -2,6 +2,18 @@ import ngModule from '../module'; import Section from 'salix/components/section'; export default class Controller extends Section { + $onInit() { + if (this.$params.emailVerified) { + const params = { + emailVerified: true + }; + return this.$http.patch(`Accounts/${this.$params.id}`, params) + .then(() => { + this.vnApp.showSuccess(this.$t('Data saved!')); + }); + } + } + onSubmit() { this.$.watcher.submit() .then(() => this.card.reload()); diff --git a/modules/account/front/descriptor/index.js b/modules/account/front/descriptor/index.js index e61b8fa4e..ce4a60305 100644 --- a/modules/account/front/descriptor/index.js +++ b/modules/account/front/descriptor/index.js @@ -30,7 +30,7 @@ class Controller extends Descriptor { } $onInit() { - if (this.$location.$$search.access_token) + if (this.$params.access_token) this.onChangePassClick(false); } diff --git a/modules/account/front/descriptor/index.spec.js b/modules/account/front/descriptor/index.spec.js index 6c1df8fc7..d330f6287 100644 --- a/modules/account/front/descriptor/index.spec.js +++ b/modules/account/front/descriptor/index.spec.js @@ -108,7 +108,7 @@ describe('component vnUserDescriptor', () => { describe('onInit()', () => { it('should open onChangePassClick popup', () => { - controller.$location = {$$search: {access_token: 'RANDOM_TOKEN'}}; + controller.$params = {access_token: 'RANDOM_TOKEN'}; jest.spyOn(controller, 'onChangePassClick'); controller.$onInit(); diff --git a/modules/account/front/routes.json b/modules/account/front/routes.json index 66b26f427..c5a3d159b 100644 --- a/modules/account/front/routes.json +++ b/modules/account/front/routes.json @@ -73,7 +73,7 @@ } }, { - "url": "/basic-data", + "url": "/basic-data?access_token&emailVerified", "state": "account.card.basicData", "component": "vn-user-basic-data", "description": "Basic data", @@ -249,4 +249,4 @@ "acl": ["developer"] } ] -} \ No newline at end of file +}