refs #5475 feat(account_changePassword): accessScope and backTest
gitea/salix/pipeline/head There was a failure building this commit Details

This commit is contained in:
Alex Moreno 2023-06-19 15:06:06 +02:00
parent ee1b901dea
commit 092471f74e
11 changed files with 96 additions and 81 deletions

View File

@ -32,7 +32,7 @@ module.exports = Self => {
}); });
Self.validateAuth = async function(username, password, code) { Self.validateAuth = async function(username, password, code) {
await Self.validateCode(code); await Self.validateCode(username, code);
return Self.validateLogin(username, password); return Self.validateLogin(username, password);
}; };
@ -52,7 +52,7 @@ module.exports = Self => {
const user = await Self.findById(authCode.userFk, { const user = await Self.findById(authCode.userFk, {
fields: ['name', 'twoFactor'] fields: ['name', 'twoFactor']
}); });
console.log(username, code);
if (user.name !== username) if (user.name !== username)
throw new UserError('Authentication failed'); throw new UserError('Authentication failed');

View File

@ -153,41 +153,10 @@ module.exports = function(Self) {
} }
}; };
// Self.sharedClass._methods.find(method => method.name == 'changePassword')
// .accessScopes = ['change-password'];
Self.sharedClass._methods.find(method => method.name == 'changePassword').ctor.settings.acls = Self.sharedClass._methods.find(method => method.name == 'changePassword').ctor.settings.acls =
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'); .filter(acl => acl.property != 'changePassword');
const _changePassword = Self.prototype.ChangePassword;
Self.prototype.changePassword = async function(oldPassword, newPassword, options, cb) {
if (cb === undefined && typeof options === 'function') {
cb = options;
options = undefined;
}
const myOptions = {};
let tx;
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
options = myOptions;
try {
await _changePassword.call(this, oldPassword, newPassword, options);
tx && await tx.commit();
cb && cb();
} catch (err) {
tx && await tx.rollback();
if (cb) cb(err); else throw err;
}
};
// FIXME: https://redmine.verdnatura.es/issues/5761 // FIXME: https://redmine.verdnatura.es/issues/5761
// Self.afterRemote('prototype.patchAttributes', async(ctx, instance) => { // Self.afterRemote('prototype.patchAttributes', async(ctx, instance) => {
// if (!ctx.args || !ctx.args.data.email) return; // if (!ctx.args || !ctx.args.data.email) return;

View File

@ -24,8 +24,8 @@
<vn-textfield <vn-textfield
ng-if="$ctrl.$state.params.twoFactor" ng-if="$ctrl.$state.params.twoFactor"
label="Verification code" label="Verification code"
ng-model="$ctrl.verificationCode" ng-model="$ctrl.code"
vn-name="verificationCode" vn-name="code"
autocomplete="false" autocomplete="false"
class="vn-mt-md"> class="vn-mt-md">
</vn-textfield> </vn-textfield>

View File

@ -15,11 +15,6 @@ export default class Controller {
} }
$onInit() { $onInit() {
this.oldPassword = 'nightmare';
this.repeatPassword = 'test.1234';
this.newPassword = 'test.1234';
this.verificationCode = '1234';
console.log(this.$state.params);
if (!this.$state.params.id) if (!this.$state.params.id)
this.$state.go('login'); this.$state.go('login');
@ -34,7 +29,7 @@ export default class Controller {
const oldPassword = this.oldPassword; const oldPassword = this.oldPassword;
const newPassword = this.newPassword; const newPassword = this.newPassword;
const repeatPassword = this.repeatPassword; const repeatPassword = this.repeatPassword;
const verificationCode = this.verificationCode; const code = this.code;
if (!oldPassword || !newPassword || !repeatPassword) if (!oldPassword || !newPassword || !repeatPassword)
throw new UserError(`You must fill all the fields`); throw new UserError(`You must fill all the fields`);
@ -46,18 +41,13 @@ export default class Controller {
const headers = { const headers = {
Authorization: this.$state.params.id Authorization: this.$state.params.id
}; };
console.log({
id: userId,
oldPassword,
newPassword,
verificationCode
});
this.$http.patch('Accounts/change-password', this.$http.patch('Accounts/change-password',
{ {
id: userId, id: userId,
oldPassword, oldPassword,
newPassword, newPassword,
verificationCode code
}, },
{headers} {headers}
).then(() => { ).then(() => {

View File

@ -5,6 +5,7 @@ Repeat password: Repetir contraseña
Passwords don't match: Las contraseñas no coinciden Passwords don't match: Las contraseñas no coinciden
You must fill all the fields: Debes rellenar todos los campos You must fill all the fields: Debes rellenar todos los campos
You can't use the same password: No puedes usar la misma contraseña You can't use the same password: No puedes usar la misma contraseña
Verification code: Código de verificación
Password updated!: ¡Contraseña actualizada! Password updated!: ¡Contraseña actualizada!
Password requirements: > Password requirements: >
La contraseña debe tener al menos {{ length }} caracteres de longitud, La contraseña debe tener al menos {{ length }} caracteres de longitud,

View File

@ -16,27 +16,30 @@ module.exports = Self => {
description: 'The new password', description: 'The new password',
required: true required: true
}, { }, {
arg: 'verificationCode', arg: 'code',
type: 'string', type: 'string',
description: 'The 2FA code' description: 'The 2FA code'
} }
], ],
http: { http: {
path: `/changePassword`, path: `/change-password`,
verb: 'PATCH' verb: 'PATCH'
} }
}); });
Self.changePassword = async function(ctx, oldPassword, newPassword, verificationCode) { Self.changePassword = async function(ctx, oldPassword, newPassword, code, options) {
const userId = ctx.req.accessToken.userId; const userId = ctx.req.accessToken.userId;
const {vnUser} = Self.app.models;
const myOptions = {...(options || {})};
const {VnUser} = Self.app.models;
if (oldPassword == newPassword) if (oldPassword == newPassword)
throw new UserError(`You can't use the same password`); throw new UserError(`You can't use the same password`);
const user = await vnUser.findById(userId, {fields: ['name', 'twoFactor']}); const user = await VnUser.findById(userId, {fields: ['name', 'twoFactor']}, myOptions);
if (user.twoFactor) if (user.twoFactor)
await vnUser.validateCode(user.name, verificationCode); await VnUser.validateCode(user.name, code, myOptions);
await vnUser.changePassword(userId, oldPassword, newPassword); await VnUser.changePassword(userId, oldPassword, newPassword, myOptions);
}; };
}; };

View File

@ -1,11 +1,20 @@
const {models} = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
describe('account changePassword()', () => { fdescribe('account changePassword()', () => {
let ctx = {req: {accessToken: {userId: 70}}};
describe('Without 2FA', () => {
it('should throw an error when old password is wrong', async() => { it('should throw an error when old password is wrong', async() => {
const tx = await models.Account.beginTransaction({});
let error; let error;
try { try {
await models.Account.changePassword(1, 'wrongPassword', 'nightmare.9999'); const options = {transaction: tx};
await models.Account.changePassword(ctx, 'wrongPassword', 'nightmare.9999', null, options);
await tx.rollback();
} catch (e) { } catch (e) {
await tx.rollback();
error = e.message; error = e.message;
} }
@ -13,10 +22,60 @@ describe('account changePassword()', () => {
}); });
it('should change password', async() => { it('should change password', async() => {
const tx = await models.Account.beginTransaction({});
try { try {
await models.Account.changePassword(70, 'nightmare', 'nightmare.9999'); const options = {transaction: tx};
await models.Account.changePassword(ctx, 'nightmare', 'nightmare.9999', null, options);
await tx.rollback();
} catch (e) { } catch (e) {
await tx.rollback();
expect(e).toBeUndefined(); expect(e).toBeUndefined();
} }
}); });
});
describe('With 2FA', () => {
it('should throw an error when code is incorrect', async() => {
const tx = await models.Account.beginTransaction({});
let error;
try {
const options = {transaction: tx};
await models.VnUser.updateAll(
{id: 70},
{twoFactor: 'email'}
, options);
await models.Account.changePassword(ctx, 'wrongPassword', 'nightmare.9999', null, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e.message;
}
expect(error).toContain('Invalid current password');
});
it('should change password when code is correct', async() => {
const tx = await models.Account.beginTransaction({});
try {
const options = {transaction: tx};
await models.VnUser.updateAll(
{id: 70},
{twoFactor: 'email'}
, options);
await models.VnUser.signin('trainee', 'nightmare', options);
const authCode = await models.AuthCode.findOne({where: {userFk: 70}}, options);
await models.Account.changePassword(ctx, 'nightmare', 'nightmare.9999', authCode.code, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
expect(e).toBeUndefined();
}
});
});
}); });

View File

@ -37,13 +37,6 @@
"principalType": "ROLE", "principalType": "ROLE",
"principalId": "$authenticated", "principalId": "$authenticated",
"permission": "ALLOW" "permission": "ALLOW"
},
{
"property": "changePassword",
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
} }
] ]
} }

View File

@ -28,7 +28,7 @@ module.exports = Self => {
const isAccount = await models.Account.findById(id); const isAccount = await models.Account.findById(id);
if (isClient && !isAccount) if (isClient && !isAccount)
await models.Account.setPassword(id, newPassword); await models.VnUser.setPassword(id, newPassword);
else else
throw new UserError(`Modifiable password only via recovery or by an administrator`); throw new UserError(`Modifiable password only via recovery or by an administrator`);
}; };

2
package-lock.json generated
View File

@ -6,7 +6,7 @@
"packages": { "packages": {
"": { "": {
"name": "salix-back", "name": "salix-back",
"version": "23.24.01", "version": "23.26.01",
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": { "dependencies": {
"axios": "^1.2.2", "axios": "^1.2.2",

View File

@ -15,7 +15,7 @@ module.exports = {
type: String type: String
}, },
ip: { ip: {
type: Number type: String
} }
} }
}; };