diff --git a/loopback/locale/en.json b/loopback/locale/en.json index 53b1a8bb5..31b954a32 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -220,5 +220,7 @@ "Shelving not valid": "Shelving not valid", "printerNotExists": "The printer does not exist", "There are not picking tickets": "There are not picking tickets", - "ticketCommercial": "The ticket {{ ticket }} for the salesperson {{ salesMan }} is in preparation. (automatically generated message)" + "ticketCommercial": "The ticket {{ ticket }} for the salesperson {{ salesMan }} is in preparation. (automatically generated message)", + "This password can only be changed by the user themselves": "This password can only be changed by the user themselves", + "They're not your subordinate": "They're not your subordinate" } diff --git a/loopback/locale/es.json b/loopback/locale/es.json index 3748b6eaf..7730d4a8c 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -346,5 +346,7 @@ "CountryFK cannot be empty": "El país no puede estar vacío", "Cmr file does not exist": "El archivo del cmr no existe", "You are not allowed to modify the alias": "No estás autorizado a modificar el alias", - "The address of the customer must have information about Incoterms and Customs Agent": "El consignatario del cliente debe tener informado Incoterms y Agente de aduanas" -} \ No newline at end of file + "The address of the customer must have information about Incoterms and Customs Agent": "El consignatario del cliente debe tener informado Incoterms y Agente de aduanas", + "This password can only be changed by the user themselves": "Esta contraseña solo puede ser modificada por el propio usuario", + "They're not your subordinate": "No es tu subordinado/a." +} diff --git a/modules/account/back/models/account.js b/modules/account/back/models/account.js index 5021a5d94..ceb26053c 100644 --- a/modules/account/back/models/account.js +++ b/modules/account/back/models/account.js @@ -1,4 +1,7 @@ +const ForbiddenError = require('vn-loopback/util/forbiddenError'); +const {models} = require('vn-loopback/server/server'); + module.exports = Self => { require('../methods/account/sync')(Self); require('../methods/account/sync-by-id')(Self); @@ -7,4 +10,11 @@ module.exports = Self => { require('../methods/account/logout')(Self); require('../methods/account/change-password')(Self); require('../methods/account/set-password')(Self); + + Self.setUnverifiedPassword = async(id, pass, options) => { + const {emailVerified} = await models.VnUser.findById(id, {fields: ['emailVerified']}, options); + if (emailVerified) throw new ForbiddenError('This password can only be changed by the user themselves'); + + await models.VnUser.setPassword(id, pass, options); + }; }; diff --git a/modules/worker/back/methods/worker/setPassword.js b/modules/worker/back/methods/worker/setPassword.js index 43d3d946f..9969530a4 100644 --- a/modules/worker/back/methods/worker/setPassword.js +++ b/modules/worker/back/methods/worker/setPassword.js @@ -1,31 +1,29 @@ -const UserError = require('vn-loopback/util/user-error'); +const ForbiddenError = require('vn-loopback/util/forbiddenError'); module.exports = Self => { Self.remoteMethodCtx('setPassword', { description: 'Set a new password', - accepts: [ - { - arg: 'workerFk', - type: 'number', - required: true, - description: 'The worker id', - }, - { - arg: 'newPass', - type: 'String', - required: true, - description: 'The new worker password' - } - ], + accepts: [{ + arg: 'id', + type: 'number', + required: true, + description: 'The worker id', + http: {source: 'path'} + }, { + arg: 'newPass', + type: 'String', + required: true, + description: 'The new worker password' + }], http: { path: `/:id/setPassword`, verb: 'PATCH' } }); - Self.setPassword = async(ctx, options) => { + Self.setPassword = async(ctx, id, newPass, options) => { const models = Self.app.models; const myOptions = {}; - const {args} = ctx; let tx; + if (typeof options == 'object') Object.assign(myOptions, options); if (!myOptions.transaction) { @@ -33,11 +31,10 @@ module.exports = Self => { myOptions.transaction = tx; } try { - const isSubordinate = await models.Worker.isSubordinate(ctx, args.workerFk, myOptions); - if (!isSubordinate) throw new UserError('You don\'t have enough privileges.'); + const isSubordinate = await Self.isSubordinate(ctx, id, myOptions); + if (!isSubordinate) throw new ForbiddenError('They\'re not your subordinate'); - await models.VnUser.setPassword(args.workerFk, args.newPass, myOptions); - await models.VnUser.updateAll({id: args.workerFk}, {emailVerified: true}, myOptions); + await models.Account.setUnverifiedPassword(id, newPass, myOptions); if (tx) await tx.commit(); } catch (e) { diff --git a/modules/worker/back/methods/worker/specs/setPassword.spec.js b/modules/worker/back/methods/worker/specs/setPassword.spec.js index fbb403b24..8d152bdd1 100644 --- a/modules/worker/back/methods/worker/specs/setPassword.spec.js +++ b/modules/worker/back/methods/worker/specs/setPassword.spec.js @@ -1,31 +1,30 @@ -const UserError = require('vn-loopback/util/user-error'); - -const models = require('vn-loopback/server/server').models; +const {models} = require('vn-loopback/server/server'); describe('worker setPassword()', () => { let ctx; + const newPass = 'H3rn4d3z#'; + const employeeId = 1; + const managerId = 20; + const administrativeId = 5; + beforeAll(() => { ctx = { req: { - accessToken: {}, + accessToken: {userId: managerId}, headers: {origin: 'http://localhost'} }, - args: {workerFk: 9} }; }); - beforeEach(() => { - ctx.req.accessToken.userId = 20; - ctx.args.newPass = 'H3rn4d3z#'; - }); - - it('should change the password', async() => { + it('should change the password if it is a subordinate and the email is not verified', async() => { const tx = await models.Worker.beginTransaction({}); try { const options = {transaction: tx}; - await models.Worker.setPassword(ctx, options); + await models.Worker.setPassword(ctx, employeeId, newPass, options); + const isNewPass = await passHasBeenChanged(employeeId, newPass, options); + expect(isNewPass).toBeTrue(); await tx.rollback(); } catch (e) { await tx.rollback(); @@ -33,29 +32,48 @@ describe('worker setPassword()', () => { } }); - it('should throw an error: Password does not meet requirements', async() => { - const tx = await models.Collection.beginTransaction({}); - ctx.args.newPass = 'Hi'; + it('should not change the password if it is a subordinate and the email is verified', async() => { + const tx = await models.Worker.beginTransaction({}); + try { const options = {transaction: tx}; - await models.Worker.setPassword(ctx, options); + await models.VnUser.updateAll({id: employeeId}, {emailVerified: true}, options); + await models.Worker.setPassword(ctx, employeeId, newPass, options); + + await tx.rollback(); + } catch (e) { + expect(e.message).toEqual(`This password can only be changed by the user themselves`); + await tx.rollback(); + } + }); + + it('should not change the password if it is not a subordinate', async() => { + const tx = await models.Worker.beginTransaction({}); + try { + const options = {transaction: tx}; + await models.Worker.setPassword(ctx, administrativeId, newPass, options); + await tx.rollback(); + } catch (e) { + expect(e.message).toEqual(`They're not your subordinate`); + await tx.rollback(); + } + }); + + it('should throw an error: Password does not meet requirements', async() => { + const tx = await models.Worker.beginTransaction({}); + const newPass = 'Hi'; + try { + const options = {transaction: tx}; + await models.Worker.setPassword(ctx, employeeId, newPass, options); await tx.rollback(); } catch (e) { expect(e.sqlMessage).toEqual('Password does not meet requirements'); await tx.rollback(); } }); - - it('should throw an error: You don\'t have enough privileges.', async() => { - ctx.req.accessToken.userId = 5; - const tx = await models.Collection.beginTransaction({}); - try { - const options = {transaction: tx}; - await models.Worker.setPassword(ctx, options); - await tx.rollback(); - } catch (e) { - expect(e).toEqual(new UserError(`You don't have enough privileges.`)); - await tx.rollback(); - } - }); }); + +const passHasBeenChanged = async(userId, pass, options) => { + const user = await models.VnUser.findById(userId, null, options); + return user.hasPassword(pass); +}; diff --git a/modules/worker/front/descriptor/index.html b/modules/worker/front/descriptor/index.html index 8290e2a15..73332efac 100644 --- a/modules/worker/front/descriptor/index.html +++ b/modules/worker/front/descriptor/index.html @@ -11,8 +11,8 @@ ? 'Click to allow the user to be disabled' : 'Click to exclude the user from getting disabled'}} - - Change password + + Change password diff --git a/modules/worker/front/descriptor/index.js b/modules/worker/front/descriptor/index.js index 13ffa6f2f..d7962369c 100644 --- a/modules/worker/front/descriptor/index.js +++ b/modules/worker/front/descriptor/index.js @@ -69,6 +69,7 @@ class Controller extends Descriptor { } ] }; + return this.getData(`Workers/${this.id}`, {filter}) .then(res => this.entity = res.data); } @@ -86,15 +87,14 @@ class Controller extends Descriptor { if (this.newPassword != this.repeatPassword) throw new UserError(`Passwords don't match`); this.$http.patch( - `Workers/${this.entity.id}/setPassword`, - {workerFk: this.entity.id, newPass: this.newPassword} + `Workers/${this.entity.id}/setPassword`, {newPass: this.newPassword} ) .then(() => { this.vnApp.showSuccess(this.$translate.instant('Password changed!')); - }); + }).then(() => this.loadData()); } } -Controller.$inject = ['$element', '$scope', '$rootScope']; +Controller.$inject = ['$element', '$scope', '$rootScope', 'vnConfig']; ngModule.vnComponent('vnWorkerDescriptor', { template: require('./index.html'), diff --git a/modules/worker/front/descriptor/index.spec.js b/modules/worker/front/descriptor/index.spec.js index d158a9e8e..4f7fa6a05 100644 --- a/modules/worker/front/descriptor/index.spec.js +++ b/modules/worker/front/descriptor/index.spec.js @@ -16,6 +16,7 @@ describe('vnWorkerDescriptor', () => { const id = 1; const response = 'foo'; + $httpBackend.whenGET('UserConfigs/getUserConfig').respond({}); $httpBackend.expectRoute('GET', `Workers/${id}`).respond(response); controller.id = id; $httpBackend.flush();