diff --git a/back/methods/vn-user/sign-in.js b/back/methods/vn-user/sign-in.js
index 0a0133b826..e4cb00623b 100644
--- a/back/methods/vn-user/sign-in.js
+++ b/back/methods/vn-user/sign-in.js
@@ -1,4 +1,5 @@
const ForbiddenError = require('vn-loopback/util/forbiddenError');
+const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('signin', {
@@ -26,7 +27,6 @@ module.exports = Self => {
});
Self.signin = async function(ctx, user, password) {
- const $ = Self.app.models;
const usesEmail = user.indexOf('@') !== -1;
const where = usesEmail
@@ -34,30 +34,70 @@ module.exports = Self => {
: {name: user};
const vnUser = await Self.findOne({
- fields: ['id', 'active', 'email', 'password', 'twoFactor'],
+ fields: ['id', 'name', 'password', 'active', 'email', 'passExpired', 'twoFactor'],
where
});
- if (vnUser && vnUser.twoFactor === 'email') {
+ const validCredentials = vnUser
+ && await vnUser.hasPassword(password);
+
+ if (validCredentials) {
+ if (!vnUser.active)
+ throw new UserError('User disabled');
+ await Self.sendTwoFactor(ctx, vnUser);
+ await Self.passExpired(vnUser);
+
+ if (vnUser.twoFactor)
+ throw new ForbiddenError('REQUIRES_2FA');
+ }
+
+ return Self.validateLogin(user, password);
+ };
+
+ 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: ['change-password'],
+ userId: vnUser.id
+ });
+ throw new UserError('Pass expired', 'passExpired', {
+ id: vnUser.id,
+ token: changePasswordToken.id,
+ twoFactor: vnUser.twoFactor ? true : false
+ });
+ }
+ };
+
+ Self.sendTwoFactor = async(ctx, vnUser) => {
+ if (vnUser.twoFactor === 'email') {
+ const $ = Self.app.models;
+
const code = String(Math.floor(Math.random() * 999999));
const maxTTL = ((60 * 1000) * 5); // 5 min
await $.AuthCode.upsertWithWhere({userFk: vnUser.id}, {
userFk: vnUser.id,
code: code,
- expires: Date.now() + maxTTL
+ expires: Date.vnNow() + maxTTL
});
+ const headers = ctx.req.headers;
+ let platform = headers['sec-ch-ua-platform']?.replace(/['"=]+/g, '');
+ let browser = headers['sec-ch-ua']?.replace(/['"=]+/g, '');
const params = {
- recipientId: vnUser.id,
- recipient: vnUser.email,
- code: code
+ args: {
+ recipientId: vnUser.id,
+ recipient: vnUser.email,
+ code: code,
+ ip: ctx.req?.connection?.remoteAddress,
+ device: platform && browser ? platform + ', ' + browser : headers['user-agent'],
+ },
+ req: {getLocale: ctx.req.getLocale}
};
- ctx.args = {...ctx.args, ...params};
- await Self.sendTemplate(ctx, 'auth-code');
-
- throw new ForbiddenError('REQUIRES_2FA');
+ await Self.sendTemplate(params, 'auth-code');
}
-
- return Self.validateLogin(user, password);
};
};
diff --git a/back/methods/vn-user/validate-auth.js b/back/methods/vn-user/validate-auth.js
index 312f1347af..cc78e983e4 100644
--- a/back/methods/vn-user/validate-auth.js
+++ b/back/methods/vn-user/validate-auth.js
@@ -1,7 +1,7 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
- Self.remoteMethodCtx('validateAuth', {
+ Self.remoteMethod('validateAuth', {
description: 'Login a user with username/email and password',
accepts: [
{
@@ -31,8 +31,13 @@ module.exports = Self => {
}
});
- Self.validateAuth = async function(ctx, username, password, code) {
- const {AuthCode, UserAccess} = Self.app.models;
+ Self.validateAuth = async function(username, password, code) {
+ await Self.validateCode(code);
+ return Self.validateLogin(username, password);
+ };
+
+ Self.validateCode = async(username, code) => {
+ const {AuthCode} = Self.app.models;
const authCode = await AuthCode.findOne({
where: {
@@ -47,27 +52,10 @@ 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');
- const headers = ctx.req.headers;
- let platform = headers['sec-ch-ua-platform'];
- let browser = headers['sec-ch-ua'];
-
- if (platform) platform = platform.replace(/['"]+/g, '');
- if (browser) browser = browser.split(';')[0].replace(/['"]+/g, '');
-
- await UserAccess.upsertWithWhere({userFk: authCode.userFk}, {
- userFk: authCode.userFk,
- ip: ctx.req.connection.remoteAddress,
- agent: headers['user-agent'],
- platform: platform,
- browser: browser
- });
-
await authCode.destroy();
-
- return Self.validateLogin(username, password);
};
};
diff --git a/back/model-config.json b/back/model-config.json
index 504107f895..fd8ff2f209 100644
--- a/back/model-config.json
+++ b/back/model-config.json
@@ -116,9 +116,6 @@
"Town": {
"dataSource": "vn"
},
- "UserAccess": {
- "dataSource": "vn"
- },
"Url": {
"dataSource": "vn"
},
diff --git a/back/models/user-access.json b/back/models/user-access.json
deleted file mode 100644
index f60d702518..0000000000
--- a/back/models/user-access.json
+++ /dev/null
@@ -1,36 +0,0 @@
-{
- "name": "UserAccess",
- "base": "VnModel",
- "options": {
- "mysql": {
- "table": "salix.userAccess"
- }
- },
- "properties": {
- "userFk": {
- "type": "number",
- "required": true,
- "id": true
- },
- "ip": {
- "type": "string",
- "required": true
- },
- "agent": {
- "type": "string"
- },
- "platform": {
- "type": "string"
- },
- "browser": {
- "type": "string"
- }
- },
- "relations": {
- "user": {
- "type": "belongsTo",
- "model": "Account",
- "foreignKey": "userFk"
- }
- }
-}
diff --git a/back/models/vn-user.js b/back/models/vn-user.js
index 139ac3320c..75490ccf84 100644
--- a/back/models/vn-user.js
+++ b/back/models/vn-user.js
@@ -1,6 +1,7 @@
const vnModel = require('vn-loopback/common/models/vn-model');
const LoopBackContext = require('loopback-context');
const {Email} = require('vn-print');
+const UserError = require('vn-loopback/util/user-error');
module.exports = function(Self) {
vnModel(Self);
@@ -110,53 +111,11 @@ module.exports = function(Self) {
});
Self.validateLogin = async function(user, password) {
- const models = Self.app.models;
const usesEmail = user.indexOf('@') !== -1;
- let token;
const userInfo = usesEmail
? {email: user}
: {username: user};
- const instance = await Self.findOne({
- fields: ['username', 'password'],
- where: userInfo
- });
-
- const where = usesEmail
- ? {email: user}
- : {name: user};
- const vnUser = await Self.findOne({
- fields: ['id', 'active', 'passExpired'],
- where
- });
-
- const today = Date.vnNew();
- today.setHours(0, 0, 0, 0);
-
- const validCredentials = instance
- && await instance.hasPassword(password);
-
- if (validCredentials) {
- if (!vnUser.active)
- throw new UserError('User disabled');
-
- if (vnUser.passExpired && vnUser.passExpired.getTime() <= today.getTime()) {
- const changePasswordToken = await models.AccessToken.create({
- scopes: ['change-password'],
- userId: vnUser.id
- });
- throw new UserError('Pass expired', 'passExpired', {
- id: vnUser.id,
- token: changePasswordToken.id
- });
- }
-
- try {
- await models.Account.sync(instance.username, password);
- } catch (err) {
- console.warn(err);
- }
- }
let loginInfo = Object.assign({password}, userInfo);
token = await Self.login(loginInfo, 'user');
@@ -197,6 +156,50 @@ module.exports = function(Self) {
Self.sharedClass._methods.find(method => method.name == 'changePassword')
.accessScopes = ['change-password'];
+ Self.sharedClass._methods.find(method => method.name == 'changePassword').accepts.splice(3, 0, {
+ arg: 'verificationCode',
+ type: 'string'
+ });
+ const _changePassword = Self.changePassword;
+ Self.changePassword = async(userId, oldPassword, newPassword, verificationCode, options, cb) => {
+ if (oldPassword == newPassword)
+ throw new UserError(`You can't use the same password`);
+
+ const user = await this.findById(userId, {fields: ['name', 'twoFactor']});
+ if (user.twoFactor)
+ await Self.validateCode(user.name, verificationCode);
+
+ await _changePassword.call(this, userId, oldPassword, newPassword, options, cb);
+ };
+
+ const _prototypeChangePassword = 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 _prototypeChangePassword.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) => {
diff --git a/db/changes/232001/00-authCode.sql b/db/changes/232601/00-authCode.sql
similarity index 50%
rename from db/changes/232001/00-authCode.sql
rename to db/changes/232601/00-authCode.sql
index 0415c90f05..3a85ce58ff 100644
--- a/db/changes/232001/00-authCode.sql
+++ b/db/changes/232601/00-authCode.sql
@@ -11,17 +11,3 @@ create table `salix`.`authCode`
foreign key (userFk) references `account`.`user` (id)
on update cascade on delete cascade
);
-
-create table `salix`.`userAccess`
-(
- userFk int UNSIGNED not null,
- ip VARCHAR(25) not null,
- agent text null,
- platform VARCHAR(25) null,
- browser VARCHAR(25) null,
- constraint userAccess_pk
- primary key (userFk),
- constraint userAccess_user_null_fk
- foreign key (userFk) references `account`.`user` (id)
-)
- auto_increment = 0;
\ No newline at end of file
diff --git a/db/changes/232001/00-department.sql b/db/changes/232601/00-department.sql
similarity index 100%
rename from db/changes/232001/00-department.sql
rename to db/changes/232601/00-department.sql
diff --git a/db/changes/232001/00-user.sql b/db/changes/232601/00-user.sql
similarity index 100%
rename from db/changes/232001/00-user.sql
rename to db/changes/232601/00-user.sql
diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql
index a6557ff895..4c41c5a063 100644
--- a/db/dump/fixtures.sql
+++ b/db/dump/fixtures.sql
@@ -2821,8 +2821,8 @@ INSERT INTO `vn`.`profileType` (`id`, `name`)
INSERT INTO `salix`.`url` (`appName`, `environment`, `url`)
VALUES
- ('lilium', 'dev', 'http://localhost:9000/#/'),
- ('salix', 'dev', 'http://localhost:5000/#!/');
+ ('lilium', 'development', 'http://localhost:9000/#/'),
+ ('salix', 'development', 'http://localhost:5000/#!/');
INSERT INTO `vn`.`report` (`id`, `name`, `paperSizeFk`, `method`)
VALUES
diff --git a/front/salix/components/change-password/index.html b/front/salix/components/change-password/index.html
index 8d338d4118..026374bca0 100644
--- a/front/salix/components/change-password/index.html
+++ b/front/salix/components/change-password/index.html
@@ -21,6 +21,14 @@
type="password"
autocomplete="false">
+