feat(account): send verifyEmail when change email and tests
gitea/salix/pipeline/head There was a failure building this commit Details

This commit is contained in:
Alex Moreno 2022-09-26 08:21:58 +02:00
parent c79f884526
commit fc5460dc9e
11 changed files with 123 additions and 14 deletions

View File

@ -38,11 +38,21 @@ module.exports = Self => {
userId: user.id userId: user.id
}); });
const title = $t('Recover password');
const body = `
<p>
${$t('Click on the following link to change your password')}:
</p>
</b>
<a href="${origin}/#!/account/${user.id}/basic-data?access_token=${token.id}">
${title}
</a>`;
await Self.rawSql(`CALL vn.mail_insert(?,?,?,?)`, [ await Self.rawSql(`CALL vn.mail_insert(?,?,?,?)`, [
email, email,
null, null,
$t('Recovery password'), title,
`${origin}/#!/account/${user.id}/basic-data?access_token=${token.id}` body
]); ]);
return; return;

View File

@ -1,4 +1,6 @@
/* eslint max-len: ["error", { "code": 150 }]*/
const md5 = require('md5'); const md5 = require('md5');
const LoopBackContext = require('loopback-context');
module.exports = Self => { module.exports = Self => {
require('../methods/account/login')(Self); require('../methods/account/login')(Self);
@ -27,6 +29,40 @@ module.exports = Self => {
ctx.data.password = md5(ctx.data.password); 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 = `
<p>
${$t(`Click on the following link to verify this email. If you haven't requested this email, just ignore it`)}:
</p>
</b>
<a href="${origin}/#!/account/${userId}/basic-data?emailVerified">
${title}
</a>`;
result = await Self.rawSql(`CALL vn.mail_insert(?,?,?,?)`, [
changes.email,
null,
title,
body
], ctx.options);
});
Self.remoteMethod('getCurrentUserData', { Self.remoteMethod('getCurrentUserData', {
description: 'Gets the current user data', description: 'Gets the current user data',
accepts: [ accepts: [

View File

@ -40,6 +40,9 @@
"email": { "email": {
"type": "string" "type": "string"
}, },
"emailVerified": {
"type": "boolean"
},
"created": { "created": {
"type": "date" "type": "date"
}, },

View File

@ -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', () => { 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() => { 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(); expect(result).toBeTruthy();
}); });
it('should return false if the user doesnt have the given role', async() => { 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(); 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);
});
}); });

View File

@ -1,3 +0,0 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('Account', 'recoverPassword', 'READ', 'ALLOW', 'ROLE', 'account');

View File

@ -130,5 +130,7 @@
"Descanso diario 12h.": "Daily rest 12h.", "Descanso diario 12h.": "Daily rest 12h.",
"Fichadas impares": "Odd signs", "Fichadas impares": "Odd signs",
"Descanso diario 9h.": "Daily rest 9h.", "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"
} }

View File

@ -234,5 +234,8 @@
"Descanso semanal 36h. / 72h.": "Descanso semanal 36h. / 72h.", "Descanso semanal 36h. / 72h.": "Descanso semanal 36h. / 72h.",
"Dirección incorrecta": "Dirección incorrecta", "Dirección incorrecta": "Dirección incorrecta",
"This email does not belong to a user": "Este correo electrónico no pertenece a un usuario.", "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."
} }

View File

@ -2,6 +2,18 @@ import ngModule from '../module';
import Section from 'salix/components/section'; import Section from 'salix/components/section';
export default class Controller extends 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() { onSubmit() {
this.$.watcher.submit() this.$.watcher.submit()
.then(() => this.card.reload()); .then(() => this.card.reload());

View File

@ -30,7 +30,7 @@ class Controller extends Descriptor {
} }
$onInit() { $onInit() {
if (this.$location.$$search.access_token) if (this.$params.access_token)
this.onChangePassClick(false); this.onChangePassClick(false);
} }

View File

@ -108,7 +108,7 @@ describe('component vnUserDescriptor', () => {
describe('onInit()', () => { describe('onInit()', () => {
it('should open onChangePassClick popup', () => { it('should open onChangePassClick popup', () => {
controller.$location = {$$search: {access_token: 'RANDOM_TOKEN'}}; controller.$params = {access_token: 'RANDOM_TOKEN'};
jest.spyOn(controller, 'onChangePassClick'); jest.spyOn(controller, 'onChangePassClick');
controller.$onInit(); controller.$onInit();

View File

@ -73,7 +73,7 @@
} }
}, },
{ {
"url": "/basic-data", "url": "/basic-data?access_token&emailVerified",
"state": "account.card.basicData", "state": "account.card.basicData",
"component": "vn-user-basic-data", "component": "vn-user-basic-data",
"description": "Basic data", "description": "Basic data",