From 5c777c705feecca80213dd5a7ef4d70d246e4c26 Mon Sep 17 00:00:00 2001 From: Javier Segarra Date: Tue, 14 Nov 2023 13:00:20 +0100 Subject: [PATCH 01/11] refs #6434 feat: add new error message --- back/methods/vn-user/sign-in.js | 28 +++++++++++++++------------- back/models/vn-user.js | 13 +++++++++++-- loopback/locale/es.json | 4 +++- 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/back/methods/vn-user/sign-in.js b/back/methods/vn-user/sign-in.js index b9e0d2f70..5c84b654e 100644 --- a/back/methods/vn-user/sign-in.js +++ b/back/methods/vn-user/sign-in.js @@ -26,7 +26,7 @@ module.exports = Self => { } }); - Self.signIn = async function(ctx, user, password, options) { + Self.signIn = async function (ctx, user, password, options) { const myOptions = {}; if (typeof options == 'object') Object.assign(myOptions, options); @@ -40,15 +40,17 @@ module.exports = Self => { const validCredentials = vnUser && await vnUser.hasPassword(password); - if (validCredentials) { - if (!vnUser.active) - throw new UserError('User disabled'); - await Self.sendTwoFactor(ctx, vnUser, myOptions); - await Self.passExpired(vnUser, myOptions); + if (!validCredentials) + throw new UserError('Invalid credentials'); - if (vnUser.twoFactor) - throw new ForbiddenError(null, 'REQUIRES_2FA'); - } + if (!vnUser.active) + throw new UserError('User disabled'); + + await Self.sendTwoFactor(ctx, vnUser, myOptions); + await Self.passExpired(vnUser, myOptions); + + if (vnUser.twoFactor) + throw new ForbiddenError(null, 'REQUIRES_2FA'); return Self.validateLogin(user, password); }; @@ -59,18 +61,18 @@ module.exports = Self => { if (vnUser.passExpired && vnUser.passExpired.getTime() <= today.getTime()) { const err = new UserError('Pass expired', 'passExpired'); - err.details = {userId: vnUser.id, twoFactor: vnUser.twoFactor ? true : false}; + err.details = { userId: vnUser.id, twoFactor: vnUser.twoFactor ? true : false }; throw err; } }; - Self.sendTwoFactor = async(ctx, vnUser, myOptions) => { + Self.sendTwoFactor = async (ctx, vnUser, myOptions) => { 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}, { + await $.AuthCode.upsertWithWhere({ userFk: vnUser.id }, { userFk: vnUser.id, code: code, expires: Date.vnNow() + maxTTL @@ -87,7 +89,7 @@ module.exports = Self => { ip: ctx.req?.connection?.remoteAddress, device: platform && browser ? platform + ', ' + browser : headers['user-agent'], }, - req: {getLocale: ctx.req.getLocale}, + req: { getLocale: ctx.req.getLocale }, }; await Self.sendTemplate(params, 'auth-code', true); diff --git a/back/models/vn-user.js b/back/models/vn-user.js index de5bf7b63..5c6e4a30f 100644 --- a/back/models/vn-user.js +++ b/back/models/vn-user.js @@ -2,6 +2,7 @@ const vnModel = require('vn-loopback/common/models/vn-model'); const {Email} = require('vn-print'); const ForbiddenError = require('vn-loopback/util/forbiddenError'); const LoopBackContext = require('loopback-context'); +const UserError = require('vn-loopback/util/user-error'); module.exports = function(Self) { vnModel(Self); @@ -121,10 +122,18 @@ module.exports = function(Self) { }); Self.validateLogin = async function(user, password) { - let loginInfo = Object.assign({password}, Self.userUses(user)); - token = await Self.login(loginInfo, 'user'); + const loginInfo = Object.assign({password}, Self.userUses(user)); + const token = await Self.login(loginInfo, 'user'); const userToken = await token.user.get(); + + if (userToken.username !== user) { + console.error('ERROR!!! - Signin with other user', userToken, user); + throw new UserError('Try again'); + } + + const userCheck = await Self.app.models.VnUser.findOne({where: {name: user}}); + if (userToken.id != userCheck.id) await Self.validateLogin(user, password); try { await Self.app.models.Account.sync(userToken.name, password); } catch (err) { diff --git a/loopback/locale/es.json b/loopback/locale/es.json index b42720458..7cccc0fd0 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -325,5 +325,7 @@ "The ticket is in preparation": "El ticket [{{ticketId}}]({{{ticketUrl}}}) del comercial {{salesPersonId}} está en preparación", "The amount cannot be less than the minimum": "La cantidad no puede ser menor que la cantidad mímina", "quantityLessThanMin": "La cantidad no puede ser menor que la cantidad mímina", - "The notification subscription of this worker cant be modified": "La subscripción a la notificación de este trabajador no puede ser modificada" + "The notification subscription of this worker cant be modified": "La subscripción a la notificación de este trabajador no puede ser modificada", + "User disabled": "User disabled", + "Invalid credentials": "Invalid credentials" } From 7f82243ce6000176d9b9eeab03f81d68c781530f Mon Sep 17 00:00:00 2001 From: Javier Segarra Date: Tue, 14 Nov 2023 15:00:03 +0100 Subject: [PATCH 02/11] refs #6434 feat: create signInLog table --- back/methods/vn-user/sign-in.js | 19 ++++++---- db/changes/234801/00-createSignInLogTable.sql | 19 ++++++++++ modules/account/back/model-config.json | 3 ++ modules/account/back/models/sign_in-log.json | 35 +++++++++++++++++++ 4 files changed, 69 insertions(+), 7 deletions(-) create mode 100644 db/changes/234801/00-createSignInLogTable.sql create mode 100644 modules/account/back/models/sign_in-log.json diff --git a/back/methods/vn-user/sign-in.js b/back/methods/vn-user/sign-in.js index 5c84b654e..78d74b147 100644 --- a/back/methods/vn-user/sign-in.js +++ b/back/methods/vn-user/sign-in.js @@ -26,7 +26,7 @@ module.exports = Self => { } }); - Self.signIn = async function (ctx, user, password, options) { + Self.signIn = async function(ctx, user, password, options) { const myOptions = {}; if (typeof options == 'object') Object.assign(myOptions, options); @@ -51,8 +51,13 @@ module.exports = Self => { if (vnUser.twoFactor) throw new ForbiddenError(null, 'REQUIRES_2FA'); - - return Self.validateLogin(user, password); + const validateLogin = await Self.validateLogin(user, password); + await Self.app.models.SignInLog.create({ + id: validateLogin.token, + userFk: vnUser.id, + ip: ctx.req.ip + }); + return validateLogin; }; Self.passExpired = async vnUser => { @@ -61,18 +66,18 @@ module.exports = Self => { if (vnUser.passExpired && vnUser.passExpired.getTime() <= today.getTime()) { const err = new UserError('Pass expired', 'passExpired'); - err.details = { userId: vnUser.id, twoFactor: vnUser.twoFactor ? true : false }; + err.details = {userId: vnUser.id, twoFactor: vnUser.twoFactor ? true : false}; throw err; } }; - Self.sendTwoFactor = async (ctx, vnUser, myOptions) => { + Self.sendTwoFactor = async(ctx, vnUser, myOptions) => { 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 }, { + await $.AuthCode.upsertWithWhere({userFk: vnUser.id}, { userFk: vnUser.id, code: code, expires: Date.vnNow() + maxTTL @@ -89,7 +94,7 @@ module.exports = Self => { ip: ctx.req?.connection?.remoteAddress, device: platform && browser ? platform + ', ' + browser : headers['user-agent'], }, - req: { getLocale: ctx.req.getLocale }, + req: {getLocale: ctx.req.getLocale}, }; await Self.sendTemplate(params, 'auth-code', true); diff --git a/db/changes/234801/00-createSignInLogTable.sql b/db/changes/234801/00-createSignInLogTable.sql new file mode 100644 index 000000000..977de4646 --- /dev/null +++ b/db/changes/234801/00-createSignInLogTable.sql @@ -0,0 +1,19 @@ + + +-- +-- Table structure for table `signInLog` +-- + +DROP TABLE IF EXISTS `account`.`signInLog`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `account`.`signInLog` ( + `id` varchar(10) NOT NULL , + `userFk` int(10) unsigned DEFAULT NULL, + `creationDate` timestamp NULL DEFAULT current_timestamp(), + `ip` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + PRIMARY KEY (`id`), + KEY `userFk` (`userFk`), + CONSTRAINT `signInLog_ibfk_1` FOREIGN KEY (`userFk`) REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +); + diff --git a/modules/account/back/model-config.json b/modules/account/back/model-config.json index a4eb9fa57..b4bd6dbaf 100644 --- a/modules/account/back/model-config.json +++ b/modules/account/back/model-config.json @@ -35,6 +35,9 @@ "SambaConfig": { "dataSource": "vn" }, + "SignInLog": { + "dataSource": "vn" + }, "Sip": { "dataSource": "vn" }, diff --git a/modules/account/back/models/sign_in-log.json b/modules/account/back/models/sign_in-log.json new file mode 100644 index 000000000..df9ad8153 --- /dev/null +++ b/modules/account/back/models/sign_in-log.json @@ -0,0 +1,35 @@ +{ + "name": "SignInLog", + "base": "VnModel", + "options": { + "mysql": { + "table": "account.signInLog" + } + }, + "properties": { + "id": { + "id": true, + "type": "string", + "forceId": false + }, + "creationDate": { + "type": "date" + }, + "userFk": { + "type": "number" + }, + "ip": { + "type": "string" + } + }, + "relations": { + "user": { + "type": "belongsTo", + "model": "VnUser", + "foreignKey": "userFk" + } + }, + "scope": { + "order": ["creationDate DESC", "id DESC"] + } +} From add3a81032aae8ecfa41e3fad37179c6b1dacac4 Mon Sep 17 00:00:00 2001 From: Javier Segarra Date: Wed, 15 Nov 2023 09:29:26 +0100 Subject: [PATCH 03/11] refs #6434 feat: remove recursively fn --- back/methods/vn-user/sign-in.js | 2 ++ back/models/vn-user.js | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/back/methods/vn-user/sign-in.js b/back/methods/vn-user/sign-in.js index 78d74b147..e51cf8d8e 100644 --- a/back/methods/vn-user/sign-in.js +++ b/back/methods/vn-user/sign-in.js @@ -51,7 +51,9 @@ module.exports = Self => { if (vnUser.twoFactor) throw new ForbiddenError(null, 'REQUIRES_2FA'); + const validateLogin = await Self.validateLogin(user, password); + await Self.app.models.SignInLog.create({ id: validateLogin.token, userFk: vnUser.id, diff --git a/back/models/vn-user.js b/back/models/vn-user.js index 5c6e4a30f..00f5cd0b8 100644 --- a/back/models/vn-user.js +++ b/back/models/vn-user.js @@ -132,8 +132,6 @@ module.exports = function(Self) { throw new UserError('Try again'); } - const userCheck = await Self.app.models.VnUser.findOne({where: {name: user}}); - if (userToken.id != userCheck.id) await Self.validateLogin(user, password); try { await Self.app.models.Account.sync(userToken.name, password); } catch (err) { From 5b3645a6419e765cf186b4280d22594f83acfb7d Mon Sep 17 00:00:00 2001 From: Javier Segarra Date: Wed, 13 Dec 2023 11:59:00 +0100 Subject: [PATCH 04/11] refs #6434 fix: bad merge --- back/models/vn-user.js | 27 +++++++++++++++++++ db/changes/234801/00-createSignInLogTable.sql | 19 ------------- 2 files changed, 27 insertions(+), 19 deletions(-) delete mode 100644 db/changes/234801/00-createSignInLogTable.sql diff --git a/back/models/vn-user.js b/back/models/vn-user.js index d840e34e8..7b1471e5c 100644 --- a/back/models/vn-user.js +++ b/back/models/vn-user.js @@ -125,6 +125,33 @@ module.exports = function(Self) { return email.send(); }); + /** + * Sign-in validate + * @param {String} user The user + * @param {Object} userToken Options + * @param {Object} token accessToken + * @param {Object} ctx context + */ + Self.signInValidate = async(user, userToken, token, ctx) => { + const [[key, value]] = Object.entries(Self.userUses(user)); + const isOwner = Self.rawSql(`SELECT ? = ? `, [userToken[key], value]); + await Self.app.models.SignInLog.create({ + userName: user, + token: token.id, + userFk: userToken.id, + ip: ctx.req.ip, + owner: isOwner + }); + if (!isOwner) + throw new UserError('Try again'); + }; + + /** + * Validate login params + * @param {String} user The user + * @param {String} password + * @param {Object} ctx context + */ Self.validateLogin = async function(user, password) { let loginInfo = Object.assign({password}, Self.userUses(user)); token = await Self.login(loginInfo, 'user'); diff --git a/db/changes/234801/00-createSignInLogTable.sql b/db/changes/234801/00-createSignInLogTable.sql deleted file mode 100644 index 977de4646..000000000 --- a/db/changes/234801/00-createSignInLogTable.sql +++ /dev/null @@ -1,19 +0,0 @@ - - --- --- Table structure for table `signInLog` --- - -DROP TABLE IF EXISTS `account`.`signInLog`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `account`.`signInLog` ( - `id` varchar(10) NOT NULL , - `userFk` int(10) unsigned DEFAULT NULL, - `creationDate` timestamp NULL DEFAULT current_timestamp(), - `ip` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, - PRIMARY KEY (`id`), - KEY `userFk` (`userFk`), - CONSTRAINT `signInLog_ibfk_1` FOREIGN KEY (`userFk`) REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE -); - From 5f93b8c44032a4b982d23383b8c5158f721e9817 Mon Sep 17 00:00:00 2001 From: Javier Segarra Date: Wed, 13 Dec 2023 12:01:18 +0100 Subject: [PATCH 05/11] refs #6434 fix: bad merge --- 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 7b1471e5c..e14cd30ea 100644 --- a/back/models/vn-user.js +++ b/back/models/vn-user.js @@ -152,9 +152,9 @@ module.exports = function(Self) { * @param {String} password * @param {Object} ctx context */ - Self.validateLogin = async function(user, password) { - let loginInfo = Object.assign({password}, Self.userUses(user)); - token = await Self.login(loginInfo, 'user'); + Self.validateLogin = async function(user, password, ctx) { + const loginInfo = Object.assign({password}, Self.userUses(user)); + const token = await Self.login(loginInfo, 'user'); const userToken = await token.user.get(); From 6af99b7669f0ff0ac8a5b4110993686968d11dd5 Mon Sep 17 00:00:00 2001 From: Javier Segarra Date: Wed, 13 Dec 2023 12:07:41 +0100 Subject: [PATCH 06/11] refs #6434 feat: delete records whe !owner --- db/changes/235201/00-truncate-where-signInLog.sql | 1 + 1 file changed, 1 insertion(+) create mode 100644 db/changes/235201/00-truncate-where-signInLog.sql diff --git a/db/changes/235201/00-truncate-where-signInLog.sql b/db/changes/235201/00-truncate-where-signInLog.sql new file mode 100644 index 000000000..93d80d716 --- /dev/null +++ b/db/changes/235201/00-truncate-where-signInLog.sql @@ -0,0 +1 @@ +DELETE FROM `account`.`signInLog` where owner <> FALSE From 5a36fabf058153792cda778c7e0e01c3b52588d1 Mon Sep 17 00:00:00 2001 From: Javier Segarra Date: Wed, 13 Dec 2023 12:07:57 +0100 Subject: [PATCH 07/11] refs #6434 feat: insert record just when fail --- back/models/vn-user.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/back/models/vn-user.js b/back/models/vn-user.js index e14cd30ea..1134df363 100644 --- a/back/models/vn-user.js +++ b/back/models/vn-user.js @@ -135,15 +135,16 @@ module.exports = function(Self) { Self.signInValidate = async(user, userToken, token, ctx) => { const [[key, value]] = Object.entries(Self.userUses(user)); const isOwner = Self.rawSql(`SELECT ? = ? `, [userToken[key], value]); - await Self.app.models.SignInLog.create({ - userName: user, - token: token.id, - userFk: userToken.id, - ip: ctx.req.ip, - owner: isOwner - }); - if (!isOwner) - throw new UserError('Try again'); + if (!isOwner) { + await Self.app.models.SignInLog.create({ + userName: user, + token: token.id, + userFk: userToken.id, + ip: ctx.req.ip, + owner: isOwner + }); + } + throw new UserError('Try again'); }; /** From 11b54d66af6f77158441e0627186c128d3c5234c Mon Sep 17 00:00:00 2001 From: Javier Segarra Date: Wed, 13 Dec 2023 13:31:32 +0100 Subject: [PATCH 08/11] refs #6434 fix: bad merge --- back/methods/vn-user/sign-in.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/back/methods/vn-user/sign-in.js b/back/methods/vn-user/sign-in.js index 9626e2e79..782046641 100644 --- a/back/methods/vn-user/sign-in.js +++ b/back/methods/vn-user/sign-in.js @@ -40,14 +40,11 @@ module.exports = Self => { const validCredentials = vnUser && await vnUser.hasPassword(password); - if (!validCredentials) - throw new UserError('Invalid credentials'); - - if (!vnUser.active) - throw new UserError('User disabled'); - - await Self.sendTwoFactor(ctx, vnUser, myOptions); - await Self.passExpired(vnUser, myOptions); + if (validCredentials) { + if (!vnUser.active) + throw new UserError('User disabled'); + await Self.sendTwoFactor(ctx, vnUser, myOptions); + await Self.passExpired(vnUser, myOptions); if (vnUser.twoFactor) throw new ForbiddenError(null, 'REQUIRES_2FA'); From 57b3d96628ff5a1d7618d3741373ab84d061e4a4 Mon Sep 17 00:00:00 2001 From: JAVIER SEGARRA MARTINEZ Date: Wed, 20 Dec 2023 09:27:25 +0000 Subject: [PATCH 09/11] refs #6434 fix: bad throw error --- back/models/vn-user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/back/models/vn-user.js b/back/models/vn-user.js index 7b8a308c2..b1d09f0c0 100644 --- a/back/models/vn-user.js +++ b/back/models/vn-user.js @@ -142,8 +142,8 @@ module.exports = function(Self) { ip: ctx.req.ip, owner: isOwner }); - } throw new UserError('Try again'); + } }; /** From cb35c3632802ea9b94d41f8bb9fa321ed4b5c10f Mon Sep 17 00:00:00 2001 From: JAVIER SEGARRA MARTINEZ Date: Wed, 20 Dec 2023 10:59:36 +0000 Subject: [PATCH 10/11] refs #6434 test: update tests --- back/methods/vn-user/specs/sign-in.spec.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/back/methods/vn-user/specs/sign-in.spec.js b/back/methods/vn-user/specs/sign-in.spec.js index 1c4b4af51..a14dd301e 100644 --- a/back/methods/vn-user/specs/sign-in.spec.js +++ b/back/methods/vn-user/specs/sign-in.spec.js @@ -20,10 +20,7 @@ describe('VnUser Sign-in()', () => { let ctx = {req: {accessToken: accessToken}}; let signInLog = await SignInLog.find({where: {token: accessToken.id}}); - expect(signInLog.length).toEqual(1); - expect(signInLog[0].userFk).toEqual(accessToken.userId); - expect(signInLog[0].owner).toEqual(true); - expect(login.token).toBeDefined(); + expect(signInLog.length).toEqual(0); await VnUser.logout(ctx.req.accessToken.id); }); From dc661f298bfe6c8f4f858bbee0097472435c7379 Mon Sep 17 00:00:00 2001 From: JAVIER SEGARRA MARTINEZ Date: Thu, 21 Dec 2023 09:10:18 +0000 Subject: [PATCH 11/11] refs #6434 test: update tests --- back/methods/vn-user/specs/renew-token.spec.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/back/methods/vn-user/specs/renew-token.spec.js b/back/methods/vn-user/specs/renew-token.spec.js index 674ce36f4..146f6eb0c 100644 --- a/back/methods/vn-user/specs/renew-token.spec.js +++ b/back/methods/vn-user/specs/renew-token.spec.js @@ -27,8 +27,10 @@ describe('Renew Token', () => { jasmine.clock().uninstall(); }); - it('should renew process', async() => { - jasmine.clock().mockDate(new Date(startingTime + 21600000)); + it('should renew token', async() => { + const mockDate = new Date(startingTime + 26600000); + jasmine.clock().mockDate(mockDate); + console.log(startingTime, mockDate) const {id} = await models.VnUser.renewToken(ctx); expect(id).not.toEqual(ctx.req.accessToken.id);