This commit is contained in:
parent
d6c504d3bb
commit
6ab431f8ef
|
@ -26,9 +26,11 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.signin = async function(ctx, user, password) {
|
Self.signin = async function(ctx, user, password, options) {
|
||||||
const usesEmail = user.indexOf('@') !== -1;
|
const usesEmail = user.indexOf('@') !== -1;
|
||||||
|
|
||||||
|
const myOptions = {...(options || {})};
|
||||||
|
|
||||||
const where = usesEmail
|
const where = usesEmail
|
||||||
? {email: user}
|
? {email: user}
|
||||||
: {name: user};
|
: {name: user};
|
||||||
|
@ -36,7 +38,7 @@ module.exports = Self => {
|
||||||
const vnUser = await Self.findOne({
|
const vnUser = await Self.findOne({
|
||||||
fields: ['id', 'name', 'password', 'active', 'email', 'passExpired', 'twoFactor'],
|
fields: ['id', 'name', 'password', 'active', 'email', 'passExpired', 'twoFactor'],
|
||||||
where
|
where
|
||||||
});
|
}, myOptions);
|
||||||
|
|
||||||
const validCredentials = vnUser
|
const validCredentials = vnUser
|
||||||
&& await vnUser.hasPassword(password);
|
&& await vnUser.hasPassword(password);
|
||||||
|
@ -44,8 +46,8 @@ module.exports = Self => {
|
||||||
if (validCredentials) {
|
if (validCredentials) {
|
||||||
if (!vnUser.active)
|
if (!vnUser.active)
|
||||||
throw new UserError('User disabled');
|
throw new UserError('User disabled');
|
||||||
await Self.sendTwoFactor(ctx, vnUser);
|
await Self.sendTwoFactor(ctx, vnUser, myOptions);
|
||||||
await Self.passExpired(vnUser);
|
await Self.passExpired(vnUser, myOptions);
|
||||||
|
|
||||||
if (vnUser.twoFactor)
|
if (vnUser.twoFactor)
|
||||||
throw new ForbiddenError('REQUIRES_2FA');
|
throw new ForbiddenError('REQUIRES_2FA');
|
||||||
|
@ -54,7 +56,7 @@ module.exports = Self => {
|
||||||
return Self.validateLogin(user, password);
|
return Self.validateLogin(user, password);
|
||||||
};
|
};
|
||||||
|
|
||||||
Self.passExpired = async vnUser => {
|
Self.passExpired = async(vnUser, myOptions) => {
|
||||||
const today = Date.vnNew();
|
const today = Date.vnNew();
|
||||||
today.setHours(0, 0, 0, 0);
|
today.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
@ -63,7 +65,7 @@ module.exports = Self => {
|
||||||
const changePasswordToken = await $.AccessToken.create({
|
const changePasswordToken = await $.AccessToken.create({
|
||||||
scopes: ['changePassword'],
|
scopes: ['changePassword'],
|
||||||
userId: vnUser.id
|
userId: vnUser.id
|
||||||
});
|
}, myOptions);
|
||||||
const err = new UserError('Pass expired', 'passExpired');
|
const err = new UserError('Pass expired', 'passExpired');
|
||||||
changePasswordToken.twoFactor = vnUser.twoFactor ? true : false;
|
changePasswordToken.twoFactor = vnUser.twoFactor ? true : false;
|
||||||
err.details = {token: changePasswordToken};
|
err.details = {token: changePasswordToken};
|
||||||
|
@ -71,7 +73,7 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Self.sendTwoFactor = async(ctx, vnUser) => {
|
Self.sendTwoFactor = async(ctx, vnUser, myOptions) => {
|
||||||
if (vnUser.twoFactor === 'email') {
|
if (vnUser.twoFactor === 'email') {
|
||||||
const $ = Self.app.models;
|
const $ = Self.app.models;
|
||||||
|
|
||||||
|
@ -81,7 +83,7 @@ module.exports = Self => {
|
||||||
userFk: vnUser.id,
|
userFk: vnUser.id,
|
||||||
code: code,
|
code: code,
|
||||||
expires: Date.vnNow() + maxTTL
|
expires: Date.vnNow() + maxTTL
|
||||||
});
|
}, myOptions);
|
||||||
|
|
||||||
const headers = ctx.req.headers;
|
const headers = ctx.req.headers;
|
||||||
let platform = headers['sec-ch-ua-platform']?.replace(/['"=]+/g, '');
|
let platform = headers['sec-ch-ua-platform']?.replace(/['"=]+/g, '');
|
||||||
|
@ -94,9 +96,10 @@ module.exports = Self => {
|
||||||
ip: ctx.req?.connection?.remoteAddress,
|
ip: ctx.req?.connection?.remoteAddress,
|
||||||
device: platform && browser ? platform + ', ' + browser : headers['user-agent'],
|
device: platform && browser ? platform + ', ' + browser : headers['user-agent'],
|
||||||
},
|
},
|
||||||
req: {getLocale: ctx.req.getLocale}
|
req: {getLocale: ctx.req.getLocale},
|
||||||
};
|
};
|
||||||
await Self.sendTemplate(params, 'auth-code');
|
|
||||||
|
await Self.sendTemplate(params, 'auth-code', true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
const {models} = require('vn-loopback/server/server');
|
||||||
|
|
||||||
|
fdescribe('VnUser Sign-in()', () => {
|
||||||
|
const employeeId = 1;
|
||||||
|
const unauthCtx = {
|
||||||
|
req: {
|
||||||
|
headers: {},
|
||||||
|
connection: {
|
||||||
|
remoteAddress: '127.0.0.1'
|
||||||
|
},
|
||||||
|
getLocale: () => 'en'
|
||||||
|
},
|
||||||
|
args: {}
|
||||||
|
};
|
||||||
|
const {VnUser, AccessToken} = models;
|
||||||
|
describe('when credentials are correct', () => {
|
||||||
|
it('should return the token', async() => {
|
||||||
|
let login = await VnUser.signin(unauthCtx, 'salesAssistant', 'nightmare');
|
||||||
|
let accessToken = await AccessToken.findById(login.token);
|
||||||
|
let ctx = {req: {accessToken: accessToken}};
|
||||||
|
|
||||||
|
expect(login.token).toBeDefined();
|
||||||
|
|
||||||
|
await VnUser.logout(ctx.req.accessToken.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the token if the user doesnt exist but the client does', async() => {
|
||||||
|
let login = await VnUser.signin(unauthCtx, 'PetterParker', 'nightmare');
|
||||||
|
let accessToken = await AccessToken.findById(login.token);
|
||||||
|
let ctx = {req: {accessToken: accessToken}};
|
||||||
|
|
||||||
|
expect(login.token).toBeDefined();
|
||||||
|
|
||||||
|
await VnUser.logout(ctx.req.accessToken.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when credentials are incorrect', () => {
|
||||||
|
it('should throw a 401 error', async() => {
|
||||||
|
let error;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await VnUser.signin(unauthCtx, 'IDontExist', 'TotallyWrongPassword');
|
||||||
|
} catch (e) {
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error.statusCode).toBe(401);
|
||||||
|
expect(error.code).toBe('LOGIN_FAILED');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when two-factor auth is required', () => {
|
||||||
|
it('should throw a 403 error', async() => {
|
||||||
|
const employee = await VnUser.findById(employeeId);
|
||||||
|
const tx = await VnUser.beginTransaction({});
|
||||||
|
|
||||||
|
let error;
|
||||||
|
try {
|
||||||
|
const options = {transaction: tx};
|
||||||
|
await employee.updateAttribute('twoFactor', 'email', options);
|
||||||
|
|
||||||
|
await VnUser.signin(unauthCtx, 'employee', 'nightmare', options);
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error.statusCode).toBe(403);
|
||||||
|
expect(error.message).toBe('REQUIRES_2FA');
|
||||||
|
|
||||||
|
await employee.updateAttribute('twoFactor', null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when passExpired', () => {
|
||||||
|
it('should throw a passExpired error', async() => {
|
||||||
|
let error;
|
||||||
|
const employee = await VnUser.findById(employeeId);
|
||||||
|
const yesterday = Date.vnNew();
|
||||||
|
yesterday.setDate(yesterday.getDate() - 1);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await employee.updateAttribute('passExpired', yesterday);
|
||||||
|
|
||||||
|
await VnUser.signin(unauthCtx, 'employee', 'nightmare');
|
||||||
|
} catch (e) {
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error.statusCode).toBe(400);
|
||||||
|
expect(error.message).toBe('Pass expired');
|
||||||
|
|
||||||
|
await employee.updateAttribute('passExpired', null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,74 +0,0 @@
|
||||||
const {models} = require('vn-loopback/server/server');
|
|
||||||
|
|
||||||
describe('account login()', () => {
|
|
||||||
const employeeId = 1;
|
|
||||||
const unauthCtx = {
|
|
||||||
req: {
|
|
||||||
connection: {
|
|
||||||
remoteAddress: '127.0.0.1'
|
|
||||||
},
|
|
||||||
getLocale: () => 'en'
|
|
||||||
},
|
|
||||||
args: {}
|
|
||||||
};
|
|
||||||
describe('when credentials are correct', () => {
|
|
||||||
it('should return the token', async() => {
|
|
||||||
let login = await models.VnUser.signin(unauthCtx, 'salesAssistant', 'nightmare');
|
|
||||||
let accessToken = await models.AccessToken.findById(login.token);
|
|
||||||
let ctx = {req: {accessToken: accessToken}};
|
|
||||||
|
|
||||||
expect(login.token).toBeDefined();
|
|
||||||
|
|
||||||
await models.VnUser.logout(ctx.req.accessToken.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the token if the user doesnt exist but the client does', async() => {
|
|
||||||
let login = await models.VnUser.signin(unauthCtx, 'PetterParker', 'nightmare');
|
|
||||||
let accessToken = await models.AccessToken.findById(login.token);
|
|
||||||
let ctx = {req: {accessToken: accessToken}};
|
|
||||||
|
|
||||||
expect(login.token).toBeDefined();
|
|
||||||
|
|
||||||
await models.VnUser.logout(ctx.req.accessToken.id);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when credentials are incorrect', () => {
|
|
||||||
it('should throw a 401 error', async() => {
|
|
||||||
let error;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await models.Account.login(unauthCtx, 'IDontExist', 'TotallyWrongPassword');
|
|
||||||
} catch (e) {
|
|
||||||
error = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(error).toBeDefined();
|
|
||||||
expect(error.statusCode).toBe(401);
|
|
||||||
expect(error.code).toBe('LOGIN_FAILED');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when two-factor auth is required', () => {
|
|
||||||
it('should throw a 403 error', async() => {
|
|
||||||
let error;
|
|
||||||
const Account = models.Account;
|
|
||||||
|
|
||||||
const employee = await Account.findById(employeeId);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await employee.updateAttribute('twoFactor', 'email');
|
|
||||||
|
|
||||||
await Account.login(unauthCtx, 'employee', 'nightmare');
|
|
||||||
} catch (e) {
|
|
||||||
error = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(error).toBeDefined();
|
|
||||||
expect(error.statusCode).toBe(403);
|
|
||||||
expect(error.message).toBe('REQUIRES_2FA');
|
|
||||||
|
|
||||||
await employee.updateAttribute('twoFactor', null);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,41 +1,52 @@
|
||||||
const {models} = require('vn-loopback/server/server');
|
const {models} = require('vn-loopback/server/server');
|
||||||
|
|
||||||
describe('account validateAuth()', () => {
|
fdescribe('VnUser validate-auth()', () => {
|
||||||
const developerId = 9;
|
describe('validateAuth', () => {
|
||||||
|
it('should signin if data is correct', async() => {
|
||||||
it('should throw an error for a non existent code', async() => {
|
await models.AuthCode.create({
|
||||||
const ctx = {req: {accessToken: {userId: developerId}}};
|
userFk: 9,
|
||||||
|
|
||||||
let error;
|
|
||||||
try {
|
|
||||||
await models.VnUser.validateAuth(ctx, 'developer', 'nightmare', '123456');
|
|
||||||
} catch (e) {
|
|
||||||
error = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(error).toBeDefined();
|
|
||||||
expect(error.statusCode).toBe(400);
|
|
||||||
expect(error.message).toEqual('Invalid or expired verification code');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw an error when a code doesn`t match the login username', async() => {
|
|
||||||
const ctx = {req: {accessToken: {userId: developerId}}};
|
|
||||||
|
|
||||||
let error;
|
|
||||||
try {
|
|
||||||
const authCode = await models.AuthCode.create({
|
|
||||||
userFk: 1,
|
|
||||||
code: '555555',
|
code: '555555',
|
||||||
expires: Date.vnNow() + (60 * 1000)
|
expires: Date.vnNow() + (60 * 1000)
|
||||||
});
|
});
|
||||||
await models.VnUser.validateAuth(ctx, 'developer', 'nightmare', '555555');
|
const token = await models.VnUser.validateAuth('developer', 'nightmare', '555555');
|
||||||
await authCode.destroy();
|
|
||||||
} catch (e) {
|
|
||||||
error = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(error).toBeDefined();
|
expect(token.token).toBeDefined();
|
||||||
expect(error.statusCode).toBe(400);
|
});
|
||||||
expect(error.message).toEqual('Authentication failed');
|
});
|
||||||
|
|
||||||
|
describe('validateCode', () => {
|
||||||
|
it('should throw an error for a non existent code', async() => {
|
||||||
|
let error;
|
||||||
|
try {
|
||||||
|
await models.VnUser.validateCode('developer', '123456');
|
||||||
|
} catch (e) {
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error.statusCode).toBe(400);
|
||||||
|
expect(error.message).toEqual('Invalid or expired verification code');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error when a code doesn`t match the login username', async() => {
|
||||||
|
let error;
|
||||||
|
let authCode;
|
||||||
|
try {
|
||||||
|
authCode = await models.AuthCode.create({
|
||||||
|
userFk: 1,
|
||||||
|
code: '555555',
|
||||||
|
expires: Date.vnNow() + (60 * 1000)
|
||||||
|
});
|
||||||
|
|
||||||
|
await models.VnUser.validateCode('developer', '555555');
|
||||||
|
} catch (e) {
|
||||||
|
authCode && await authCode.destroy();
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error.statusCode).toBe(400);
|
||||||
|
expect(error.message).toEqual('Authentication failed');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -31,19 +31,21 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.validateAuth = async function(username, password, code) {
|
Self.validateAuth = async(username, password, code, options) => {
|
||||||
await Self.validateCode(username, code);
|
const myOptions = {...(options || {})};
|
||||||
|
|
||||||
|
await Self.validateCode(username, code, myOptions);
|
||||||
return Self.validateLogin(username, password);
|
return Self.validateLogin(username, password);
|
||||||
};
|
};
|
||||||
|
|
||||||
Self.validateCode = async(username, code) => {
|
Self.validateCode = async(username, code, myOptions) => {
|
||||||
const {AuthCode} = Self.app.models;
|
const {AuthCode} = Self.app.models;
|
||||||
|
|
||||||
const authCode = await AuthCode.findOne({
|
const authCode = await AuthCode.findOne({
|
||||||
where: {
|
where: {
|
||||||
code: code
|
code: code
|
||||||
}
|
}
|
||||||
});
|
}, myOptions);
|
||||||
|
|
||||||
const expired = authCode && Date.vnNow() > authCode.expires;
|
const expired = authCode && Date.vnNow() > authCode.expires;
|
||||||
if (!authCode || expired)
|
if (!authCode || expired)
|
||||||
|
@ -51,11 +53,11 @@ module.exports = Self => {
|
||||||
|
|
||||||
const user = await Self.findById(authCode.userFk, {
|
const user = await Self.findById(authCode.userFk, {
|
||||||
fields: ['name', 'twoFactor']
|
fields: ['name', 'twoFactor']
|
||||||
});
|
}, myOptions);
|
||||||
|
|
||||||
if (user.name !== username)
|
if (user.name !== username)
|
||||||
throw new UserError('Authentication failed');
|
throw new UserError('Authentication failed');
|
||||||
|
|
||||||
await authCode.destroy();
|
await authCode.destroy(myOptions);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,7 +2,8 @@ INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalTyp
|
||||||
VALUES
|
VALUES
|
||||||
('VnUser','acl','READ','ALLOW','ROLE','account'),
|
('VnUser','acl','READ','ALLOW','ROLE','account'),
|
||||||
('VnUser','getCurrentUserData','READ','ALLOW','ROLE','account'),
|
('VnUser','getCurrentUserData','READ','ALLOW','ROLE','account'),
|
||||||
('VnUser','changePassword', 'WRITE', 'ALLOW', 'ROLE', 'account');
|
('VnUser','changePassword', 'WRITE', 'ALLOW', 'ROLE', 'account'), ('VnUser','changePassword', 'WRITE', 'ALLOW', 'ROLE', 'account');
|
||||||
|
('Account','exists','READ','ALLOW','ROLE','account');
|
||||||
|
|
||||||
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
|
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
|
||||||
VALUES
|
VALUES
|
||||||
|
|
|
@ -2,7 +2,7 @@ create table `salix`.`authCode`
|
||||||
(
|
(
|
||||||
userFk int UNSIGNED not null,
|
userFk int UNSIGNED not null,
|
||||||
code int not null,
|
code int not null,
|
||||||
expires TIMESTAMP not null,
|
expires bigint not null,
|
||||||
constraint authCode_pk
|
constraint authCode_pk
|
||||||
primary key (userFk),
|
primary key (userFk),
|
||||||
constraint authCode_unique
|
constraint authCode_unique
|
||||||
|
|
|
@ -77,7 +77,9 @@ INSERT INTO `account`.`user`(`id`,`name`, `nickname`, `role`,`active`,`email`, `
|
||||||
ORDER BY id;
|
ORDER BY id;
|
||||||
|
|
||||||
INSERT INTO `account`.`account`(`id`)
|
INSERT INTO `account`.`account`(`id`)
|
||||||
SELECT id FROM `account`.`user`;
|
SELECT id
|
||||||
|
FROM `account`.`user`
|
||||||
|
WHERE role <> 2;
|
||||||
|
|
||||||
INSERT INTO `vn`.`educationLevel` (`id`, `name`)
|
INSERT INTO `vn`.`educationLevel` (`id`, `name`)
|
||||||
VALUES
|
VALUES
|
||||||
|
|
|
@ -59,7 +59,7 @@ export default class Auth {
|
||||||
password: password || undefined
|
password: password || undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.$http.post('Accounts/login', params).then(
|
return this.$http.post('VnUsers/signin', params).then(
|
||||||
json => this.onLoginOk(json, remember));
|
json => this.onLoginOk(json, remember));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ export default class Controller {
|
||||||
if (newPassword != this.repeatPassword)
|
if (newPassword != this.repeatPassword)
|
||||||
throw new UserError(`Passwords don't match`);
|
throw new UserError(`Passwords don't match`);
|
||||||
if (newPassword == oldPassword)
|
if (newPassword == oldPassword)
|
||||||
throw new UserError(`You can't use the same password`);
|
throw new UserError(`You can not use the same password`);
|
||||||
|
|
||||||
const headers = {
|
const headers = {
|
||||||
Authorization: this.$state.params.id
|
Authorization: this.$state.params.id
|
||||||
|
|
|
@ -4,7 +4,7 @@ New password: Nueva contraseña
|
||||||
Repeat password: Repetir contraseña
|
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 not use the same password: No puedes usar la misma contraseña
|
||||||
Verification code: Código de verificación
|
Verification code: Código de verificación
|
||||||
Password updated!: ¡Contraseña actualizada!
|
Password updated!: ¡Contraseña actualizada!
|
||||||
Password requirements: >
|
Password requirements: >
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
Validate email auth: Autenticar email
|
Validate email auth: Autenticar email
|
||||||
Enter verification code: Introduce código de verificación
|
Enter verification code: Introduce código de verificación
|
||||||
Code: Código
|
Code: Código
|
||||||
Please enter the verification code that we have sent to your email address within 5 minutes: Por favor, introduce el código de verificación que te hemos enviado a tu email en los próximos 5 minutos
|
Please enter the verification code that we have sent to your email address within 5 minutes: Por favor, introduce el código de verificación que te hemos enviado a tu email en los próximos 5 minutos
|
||||||
|
Validate: Validar
|
||||||
|
|
|
@ -39,7 +39,7 @@ module.exports = Self => {
|
||||||
return [html, 'text/html', `filename=${fileName}.pdf"`];
|
return [html, 'text/html', `filename=${fileName}.pdf"`];
|
||||||
};
|
};
|
||||||
|
|
||||||
Self.sendTemplate = async function(ctx, templateName) {
|
Self.sendTemplate = async function(ctx, templateName, force) {
|
||||||
const args = Object.assign({}, ctx.args);
|
const args = Object.assign({}, ctx.args);
|
||||||
const params = {
|
const params = {
|
||||||
recipient: args.recipient,
|
recipient: args.recipient,
|
||||||
|
@ -52,6 +52,6 @@ module.exports = Self => {
|
||||||
|
|
||||||
const email = new Email(templateName, params);
|
const email = new Email(templateName, params);
|
||||||
|
|
||||||
return email.send({force: true});
|
return email.send({force: force});
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
const UserError = require('vn-loopback/util/user-error');
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('changePassword', {
|
Self.remoteMethodCtx('changePassword', {
|
||||||
|
@ -34,7 +35,7 @@ module.exports = Self => {
|
||||||
|
|
||||||
const {VnUser} = Self.app.models;
|
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 not use the same password`);
|
||||||
|
|
||||||
const user = await VnUser.findById(userId, {fields: ['name', 'twoFactor']}, myOptions);
|
const user = await VnUser.findById(userId, {fields: ['name', 'twoFactor']}, myOptions);
|
||||||
if (user.twoFactor)
|
if (user.twoFactor)
|
||||||
|
|
|
@ -23,5 +23,5 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.login = async(ctx, user, password) => Self.app.models.VnUser.signin(ctx, user, password);
|
Self.login = async(ctx, user, password, options) => Self.app.models.VnUser.signin(ctx, user, password, options);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,8 +1,17 @@
|
||||||
const {models} = require('vn-loopback/server/server');
|
const {models} = require('vn-loopback/server/server');
|
||||||
|
|
||||||
fdescribe('account changePassword()', () => {
|
fdescribe('account changePassword()', () => {
|
||||||
let ctx = {req: {accessToken: {userId: 70}}};
|
const ctx = {req: {accessToken: {userId: 70}}};
|
||||||
|
const unauthCtx = {
|
||||||
|
req: {
|
||||||
|
headers: {},
|
||||||
|
connection: {
|
||||||
|
remoteAddress: '127.0.0.1'
|
||||||
|
},
|
||||||
|
getLocale: () => 'en'
|
||||||
|
},
|
||||||
|
args: {}
|
||||||
|
};
|
||||||
describe('Without 2FA', () => {
|
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({});
|
const tx = await models.Account.beginTransaction({});
|
||||||
|
@ -21,6 +30,24 @@ fdescribe('account changePassword()', () => {
|
||||||
expect(error).toContain('Invalid current password');
|
expect(error).toContain('Invalid current password');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should throw an error when old and new password are the same', async() => {
|
||||||
|
const tx = await models.Account.beginTransaction({});
|
||||||
|
|
||||||
|
let error;
|
||||||
|
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 tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
error = e.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error).toContain('You can not use the same password');
|
||||||
|
});
|
||||||
|
|
||||||
it('should change password', async() => {
|
it('should change password', async() => {
|
||||||
const tx = await models.Account.beginTransaction({});
|
const tx = await models.Account.beginTransaction({});
|
||||||
|
|
||||||
|
@ -38,36 +65,27 @@ fdescribe('account changePassword()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('With 2FA', () => {
|
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() => {
|
it('should change password when code is correct', async() => {
|
||||||
const tx = await models.Account.beginTransaction({});
|
const tx = await models.Account.beginTransaction({});
|
||||||
|
const yesterday = Date.vnNew();
|
||||||
|
yesterday.setDate(yesterday.getDate() - 1);
|
||||||
|
|
||||||
|
const options = {transaction: tx};
|
||||||
try {
|
try {
|
||||||
const options = {transaction: tx};
|
|
||||||
await models.VnUser.updateAll(
|
await models.VnUser.updateAll(
|
||||||
{id: 70},
|
{id: 70},
|
||||||
{twoFactor: 'email'}
|
{
|
||||||
|
twoFactor: 'email',
|
||||||
|
passExpired: yesterday
|
||||||
|
}
|
||||||
, options);
|
, options);
|
||||||
await models.VnUser.signin('trainee', 'nightmare', options);
|
await models.VnUser.signin(unauthCtx, 'trainee', 'nightmare', options);
|
||||||
|
} catch (e) {
|
||||||
|
if (e.message != 'Pass expired')
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
const authCode = await models.AuthCode.findOne({where: {userFk: 70}}, options);
|
const authCode = await models.AuthCode.findOne({where: {userFk: 70}}, options);
|
||||||
await models.Account.changePassword(ctx, 'nightmare', 'nightmare.9999', authCode.code, options);
|
await models.Account.changePassword(ctx, 'nightmare', 'nightmare.9999', authCode.code, options);
|
||||||
await tx.rollback();
|
await tx.rollback();
|
||||||
|
|
|
@ -10,16 +10,17 @@ module.exports = {
|
||||||
|
|
||||||
async send(options) {
|
async send(options) {
|
||||||
options.from = `${config.app.senderName} <${config.app.senderEmail}>`;
|
options.from = `${config.app.senderName} <${config.app.senderEmail}>`;
|
||||||
console.log(process.env.NODE_ENV !== 'production' && !options.force);
|
if (!process.env.NODE_ENV)
|
||||||
console.log(process.env.NODE_ENV !== 'production', !options.force);
|
options.to = config.app.senderEmail;
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
|
if (process.env.NODE_ENV !== 'production' && !options.force) {
|
||||||
const notProductionError = {message: 'This not production, this email not sended'};
|
const notProductionError = {message: 'This not production, this email not sended'};
|
||||||
await this.mailLog(options, notProductionError);
|
await this.mailLog(options, notProductionError);
|
||||||
if (!config.smtp.auth.user)
|
|
||||||
return Promise.resolve(true);
|
|
||||||
|
|
||||||
options.to = config.app.senderEmail;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!config.smtp.auth.user)
|
||||||
|
return Promise.resolve(true);
|
||||||
|
|
||||||
let error;
|
let error;
|
||||||
return this.transporter.sendMail(options).catch(err => {
|
return this.transporter.sendMail(options).catch(err => {
|
||||||
error = err;
|
error = err;
|
||||||
|
|
Loading…
Reference in New Issue