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) {
await Self.validateCode(code);
await Self.validateCode(username, code);
return Self.validateLogin(username, password);
};
@ -52,7 +52,7 @@ module.exports = Self => {
const user = await Self.findById(authCode.userFk, {
fields: ['name', 'twoFactor']
});
console.log(username, code);
if (user.name !== username)
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
.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
// Self.afterRemote('prototype.patchAttributes', async(ctx, instance) => {
// if (!ctx.args || !ctx.args.data.email) return;

View File

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

View File

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

View File

@ -5,6 +5,7 @@ Repeat password: Repetir contraseña
Passwords don't match: Las contraseñas no coinciden
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
Verification code: Código de verificación
Password updated!: ¡Contraseña actualizada!
Password requirements: >
La contraseña debe tener al menos {{ length }} caracteres de longitud,

View File

@ -16,27 +16,30 @@ module.exports = Self => {
description: 'The new password',
required: true
}, {
arg: 'verificationCode',
arg: 'code',
type: 'string',
description: 'The 2FA code'
}
],
http: {
path: `/changePassword`,
path: `/change-password`,
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 {vnUser} = Self.app.models;
const myOptions = {...(options || {})};
const {VnUser} = Self.app.models;
if (oldPassword == newPassword)
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)
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,22 +1,81 @@
const {models} = require('vn-loopback/server/server');
describe('account changePassword()', () => {
it('should throw an error when old password is wrong', async() => {
let error;
try {
await models.Account.changePassword(1, 'wrongPassword', 'nightmare.9999');
} catch (e) {
error = e.message;
}
fdescribe('account changePassword()', () => {
let ctx = {req: {accessToken: {userId: 70}}};
expect(error).toContain('Invalid current password');
describe('Without 2FA', () => {
it('should throw an error when old password is wrong', async() => {
const tx = await models.Account.beginTransaction({});
let error;
try {
const options = {transaction: tx};
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', async() => {
const tx = await models.Account.beginTransaction({});
try {
const options = {transaction: tx};
await models.Account.changePassword(ctx, 'nightmare', 'nightmare.9999', null, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
expect(e).toBeUndefined();
}
});
});
it('should change password', async() => {
try {
await models.Account.changePassword(70, 'nightmare', 'nightmare.9999');
} catch (e) {
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",
"principalId": "$authenticated",
"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);
if (isClient && !isAccount)
await models.Account.setPassword(id, newPassword);
await models.VnUser.setPassword(id, newPassword);
else
throw new UserError(`Modifiable password only via recovery or by an administrator`);
};

2
package-lock.json generated
View File

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

View File

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