From d2120cdc3d772e49346a4855827b8ff102efbeee Mon Sep 17 00:00:00 2001 From: alexm Date: Mon, 17 Jul 2023 14:24:50 +0200 Subject: [PATCH 1/4] refs #5762 feat: multiplatform recover-password and change-password --- back/methods/vn-user/recover-password.js | 9 +++++++-- back/methods/vn-user/sign-in.js | 10 ++-------- back/models/vn-user.js | 11 +++++++---- front/salix/components/change-password/index.js | 14 +++----------- front/salix/components/login/index.js | 2 +- front/salix/routes.js | 2 +- .../back/methods/account/change-password.js | 13 +++++++------ .../methods/account/specs/change-password.spec.js | 14 +++++++------- 8 files changed, 35 insertions(+), 40 deletions(-) diff --git a/back/methods/vn-user/recover-password.js b/back/methods/vn-user/recover-password.js index b87bb14d4..b5d183ba3 100644 --- a/back/methods/vn-user/recover-password.js +++ b/back/methods/vn-user/recover-password.js @@ -7,6 +7,11 @@ module.exports = Self => { type: 'string', description: 'The user name or email', required: true + }, + { + arg: 'directory', + type: 'string', + description: 'The directory for mail' } ], http: { @@ -15,7 +20,7 @@ module.exports = Self => { } }); - Self.recoverPassword = async function(user) { + Self.recoverPassword = async function(user, directory) { const models = Self.app.models; const usesEmail = user.indexOf('@') !== -1; @@ -29,7 +34,7 @@ module.exports = Self => { } try { - await Self.resetPassword({email: user, emailTemplate: 'recover-password'}); + await Self.resetPassword({email: user, emailTemplate: 'recover-password', directory}); } catch (err) { if (err.code === 'EMAIL_NOT_FOUND') return; diff --git a/back/methods/vn-user/sign-in.js b/back/methods/vn-user/sign-in.js index 73cc705de..b9e0d2f70 100644 --- a/back/methods/vn-user/sign-in.js +++ b/back/methods/vn-user/sign-in.js @@ -53,19 +53,13 @@ module.exports = Self => { return Self.validateLogin(user, password); }; - Self.passExpired = async(vnUser, myOptions) => { + Self.passExpired = async vnUser => { const today = Date.vnNew(); today.setHours(0, 0, 0, 0); if (vnUser.passExpired && vnUser.passExpired.getTime() <= today.getTime()) { - const $ = Self.app.models; - const changePasswordToken = await $.AccessToken.create({ - scopes: ['changePassword'], - userId: vnUser.id - }, myOptions); const err = new UserError('Pass expired', 'passExpired'); - changePasswordToken.twoFactor = vnUser.twoFactor ? true : false; - err.details = {token: changePasswordToken}; + err.details = {userId: vnUser.id, twoFactor: vnUser.twoFactor ? true : false}; throw err; } }; diff --git a/back/models/vn-user.js b/back/models/vn-user.js index 11d4bf250..f47dc47e2 100644 --- a/back/models/vn-user.js +++ b/back/models/vn-user.js @@ -99,10 +99,13 @@ module.exports = function(Self) { const origin = headers.origin; const user = await Self.app.models.VnUser.findById(info.user.id); + let directory = info.options?.directory ?? '/#!/reset-password?access_token=$token$'; + directory = directory.replace('$token$', info.accessToken.id); + const params = { recipient: info.email, lang: user.lang, - url: `${origin}/#!/reset-password?access_token=${info.accessToken.id}` + url: origin + directory }; const options = Object.assign({}, info.options); @@ -158,9 +161,9 @@ module.exports = function(Self) { } }; - Self.sharedClass._methods.find(method => method.name == 'changePassword').ctor.settings.acls = - Self.sharedClass._methods.find(method => method.name == 'changePassword').ctor.settings.acls - .filter(acl => acl.property != 'changePassword'); + // Self.sharedClass._methods.find(method => method.name == 'changePassword').ctor.settings.acls = + // Self.sharedClass._methods.find(method => method.name == 'changePassword').ctor.settings.acls + // .filter(acl => acl.property != 'changePassword'); // FIXME: https://redmine.verdnatura.es/issues/5761 // Self.afterRemote('prototype.patchAttributes', async(ctx, instance) => { diff --git a/front/salix/components/change-password/index.js b/front/salix/components/change-password/index.js index 797d4dc96..7e30bf54e 100644 --- a/front/salix/components/change-password/index.js +++ b/front/salix/components/change-password/index.js @@ -15,9 +15,6 @@ export default class Controller { } $onInit() { - if (!this.$state.params.id) - this.$state.go('login'); - this.$http.get('UserPasswords/findOne') .then(res => { this.passRequirements = res.data; @@ -25,7 +22,7 @@ export default class Controller { } submit() { - const userId = this.$state.params.userId; + const userId = parseInt(this.$state.params.userId); const oldPassword = this.oldPassword; const newPassword = this.newPassword; const repeatPassword = this.repeatPassword; @@ -36,18 +33,13 @@ export default class Controller { if (newPassword != this.repeatPassword) throw new UserError(`Passwords don't match`); - const headers = { - Authorization: this.$state.params.id - }; - this.$http.patch('Accounts/change-password', { - id: userId, + userId, oldPassword, newPassword, code - }, - {headers} + } ).then(() => { this.vnApp.showSuccess(this.$translate.instant('Password updated!')); this.$state.go('login'); diff --git a/front/salix/components/login/index.js b/front/salix/components/login/index.js index be4fb3926..7d8cd2049 100644 --- a/front/salix/components/login/index.js +++ b/front/salix/components/login/index.js @@ -36,7 +36,7 @@ export default class Controller { const err = req.data?.error; if (err?.code == 'passExpired') - this.$state.go('change-password', err.details.token); + this.$state.go('change-password', err.details); this.loading = false; this.password = ''; diff --git a/front/salix/routes.js b/front/salix/routes.js index 5a2c030bc..8621f83c7 100644 --- a/front/salix/routes.js +++ b/front/salix/routes.js @@ -45,7 +45,7 @@ function config($stateProvider, $urlRouterProvider) { }) .state('change-password', { parent: 'outLayout', - url: '/change-password?id&userId&twoFactor', + url: '/change-password?userId&twoFactor', description: 'Change password', template: '' }) diff --git a/modules/account/back/methods/account/change-password.js b/modules/account/back/methods/account/change-password.js index a739f37d0..49af93110 100644 --- a/modules/account/back/methods/account/change-password.js +++ b/modules/account/back/methods/account/change-password.js @@ -1,12 +1,15 @@ const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { - Self.remoteMethodCtx('changePassword', { + Self.remoteMethod('changePassword', { description: 'Changes the user password', - accessType: 'WRITE', - accessScopes: ['changePassword'], accepts: [ { + arg: 'userId', + type: 'integer', + description: 'The user id', + required: true + }, { arg: 'oldPassword', type: 'string', description: 'The old password', @@ -28,9 +31,7 @@ module.exports = Self => { } }); - Self.changePassword = async function(ctx, oldPassword, newPassword, code, options) { - const userId = ctx.req.accessToken.userId; - + Self.changePassword = async function(userId, oldPassword, newPassword, code, options) { const myOptions = {}; if (typeof options == 'object') Object.assign(myOptions, options); diff --git a/modules/account/back/methods/account/specs/change-password.spec.js b/modules/account/back/methods/account/specs/change-password.spec.js index 045f98493..2fa3010af 100644 --- a/modules/account/back/methods/account/specs/change-password.spec.js +++ b/modules/account/back/methods/account/specs/change-password.spec.js @@ -1,7 +1,7 @@ const {models} = require('vn-loopback/server/server'); describe('account changePassword()', () => { - const ctx = {req: {accessToken: {userId: 70}}}; + const userId = 70; const unauthCtx = { req: { headers: {}, @@ -20,7 +20,7 @@ describe('account changePassword()', () => { try { const options = {transaction: tx}; - await models.Account.changePassword(ctx, 'wrongPassword', 'nightmare.9999', null, options); + await models.Account.changePassword(userId, 'wrongPassword', 'nightmare.9999', null, options); await tx.rollback(); } catch (e) { await tx.rollback(); @@ -37,8 +37,8 @@ describe('account changePassword()', () => { try { const options = {transaction: tx}; - await models.Account.changePassword(ctx, 'nightmare', 'nightmare.9999', null, options); - await models.Account.changePassword(ctx, 'nightmare.9999', 'nightmare.9999', null, options); + await models.Account.changePassword(userId, 'nightmare', 'nightmare.9999', null, options); + await models.Account.changePassword(userId, 'nightmare.9999', 'nightmare.9999', null, options); await tx.rollback(); } catch (e) { await tx.rollback(); @@ -54,7 +54,7 @@ describe('account changePassword()', () => { try { const options = {transaction: tx}; - await models.Account.changePassword(ctx, 'nightmare', 'nightmare.9999', null, options); + await models.Account.changePassword(userId, 'nightmare', 'nightmare.9999', null, options); await tx.rollback(); } catch (e) { await tx.rollback(); @@ -86,8 +86,8 @@ describe('account changePassword()', () => { } try { - const authCode = await models.AuthCode.findOne({where: {userFk: 70}}, options); - await models.Account.changePassword(ctx, 'nightmare', 'nightmare.9999', authCode.code, options); + const authCode = await models.AuthCode.findOne({where: {userFk: userId}}, options); + await models.Account.changePassword(userId, 'nightmare', 'nightmare.9999', authCode.code, options); await tx.rollback(); } catch (e) { await tx.rollback(); From 125d908c5d5395a5ed48900cae4ba062ce17b739 Mon Sep 17 00:00:00 2001 From: alexm Date: Mon, 17 Jul 2023 14:41:15 +0200 Subject: [PATCH 2/4] refs #5762 uncomment --- back/models/vn-user.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/back/models/vn-user.js b/back/models/vn-user.js index f47dc47e2..163649718 100644 --- a/back/models/vn-user.js +++ b/back/models/vn-user.js @@ -161,9 +161,9 @@ module.exports = function(Self) { } }; - // Self.sharedClass._methods.find(method => method.name == 'changePassword').ctor.settings.acls = - // Self.sharedClass._methods.find(method => method.name == 'changePassword').ctor.settings.acls - // .filter(acl => acl.property != 'changePassword'); + Self.sharedClass._methods.find(method => method.name == 'changePassword').ctor.settings.acls = + Self.sharedClass._methods.find(method => method.name == 'changePassword').ctor.settings.acls + .filter(acl => acl.property != 'changePassword'); // FIXME: https://redmine.verdnatura.es/issues/5761 // Self.afterRemote('prototype.patchAttributes', async(ctx, instance) => { From 3e8bb221e036e66add3b0b09a107d489ec0ad526 Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Wed, 2 Aug 2023 09:29:09 +0200 Subject: [PATCH 3/4] refs #5762 Securify fix for recovery url --- back/models/vn-user.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/back/models/vn-user.js b/back/models/vn-user.js index 163649718..3e4a08b6e 100644 --- a/back/models/vn-user.js +++ b/back/models/vn-user.js @@ -98,14 +98,22 @@ module.exports = function(Self) { const headers = httpRequest.headers; const origin = headers.origin; + const defaultHash = '/reset-password?access_token=$token$'; + const recoverHashes = { + hedera: 'verificationToken=$token$' + }; + + // FIXME: Change with: info.options?.app + const app = info.options?.directory; + let recoverHash = app ? recoverHashes[app] : defaultHash; + recoverHash = recoverHash.replace('$token$', info.accessToken.id); + const user = await Self.app.models.VnUser.findById(info.user.id); - let directory = info.options?.directory ?? '/#!/reset-password?access_token=$token$'; - directory = directory.replace('$token$', info.accessToken.id); const params = { recipient: info.email, lang: user.lang, - url: origin + directory + url: origin + '/#!' + recoverHash }; const options = Object.assign({}, info.options); From 4c199f66b2641d76c8154844551d07c98cf61fe3 Mon Sep 17 00:00:00 2001 From: alexm Date: Fri, 4 Aug 2023 14:33:38 +0200 Subject: [PATCH 4/4] refs #5762 refactor: recoverPassword use app param --- back/methods/vn-user/recover-password.js | 6 +++--- back/models/vn-user.js | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/back/methods/vn-user/recover-password.js b/back/methods/vn-user/recover-password.js index b5d183ba3..b0f7122b4 100644 --- a/back/methods/vn-user/recover-password.js +++ b/back/methods/vn-user/recover-password.js @@ -9,7 +9,7 @@ module.exports = Self => { required: true }, { - arg: 'directory', + arg: 'app', type: 'string', description: 'The directory for mail' } @@ -20,7 +20,7 @@ module.exports = Self => { } }); - Self.recoverPassword = async function(user, directory) { + Self.recoverPassword = async function(user, app) { const models = Self.app.models; const usesEmail = user.indexOf('@') !== -1; @@ -34,7 +34,7 @@ module.exports = Self => { } try { - await Self.resetPassword({email: user, emailTemplate: 'recover-password', directory}); + await Self.resetPassword({email: user, emailTemplate: 'recover-password', app}); } catch (err) { if (err.code === 'EMAIL_NOT_FOUND') return; diff --git a/back/models/vn-user.js b/back/models/vn-user.js index 7b919ec29..cf210b61b 100644 --- a/back/models/vn-user.js +++ b/back/models/vn-user.js @@ -101,8 +101,7 @@ module.exports = function(Self) { hedera: 'verificationToken=$token$' }; - // FIXME: Change with: info.options?.app - const app = info.options?.directory; + const app = info.options?.app; let recoverHash = app ? recoverHashes[app] : defaultHash; recoverHash = recoverHash.replace('$token$', info.accessToken.id);