diff --git a/back/methods/dms/downloadFile.js b/back/methods/dms/downloadFile.js index 1b9150053..d64b15b70 100644 --- a/back/methods/dms/downloadFile.js +++ b/back/methods/dms/downloadFile.js @@ -29,7 +29,8 @@ module.exports = Self => { http: { path: `/:id/downloadFile`, verb: 'GET' - } + }, + accessScopes: ['read:multimedia'] }); Self.downloadFile = async function(ctx, id) { diff --git a/back/methods/docuware/download.js b/back/methods/docuware/download.js index a0d72ce01..a1776cde5 100644 --- a/back/methods/docuware/download.js +++ b/back/methods/docuware/download.js @@ -42,7 +42,8 @@ module.exports = Self => { http: { path: `/:id/download`, verb: 'GET' - } + }, + accessScopes: ['read:multimedia'] }); Self.download = async function(id, fileCabinet, filter) { diff --git a/back/methods/image/download.js b/back/methods/image/download.js index 2b1a4b546..201e16164 100644 --- a/back/methods/image/download.js +++ b/back/methods/image/download.js @@ -47,7 +47,8 @@ module.exports = Self => { http: { path: `/:collection/:size/:id/download`, verb: 'GET' - } + }, + accessScopes: ['read:multimedia'] }); Self.download = async function(ctx, collection, size, id) { diff --git a/back/methods/vn-user/share-token.js b/back/methods/vn-user/share-token.js new file mode 100644 index 000000000..8efa22db4 --- /dev/null +++ b/back/methods/vn-user/share-token.js @@ -0,0 +1,27 @@ + +module.exports = Self => { + Self.remoteMethodCtx('shareToken', { + description: 'Returns token to view files or images and share it', + accessType: 'WRITE', + accepts: [], + returns: { + type: 'Object', + root: true + }, + http: { + path: `/shareToken`, + verb: 'GET' + } + }); + + Self.shareToken = async function(ctx) { + const {accessToken: token} = ctx.req; + + const user = await Self.findById(token.userId); + const multimediaToken = await user.accessTokens.create({ + scopes: ['read:multimedia'] + }); + + return {multimediaToken}; + }; +}; diff --git a/back/methods/vn-user/specs/share-token.spec.js b/back/methods/vn-user/specs/share-token.spec.js new file mode 100644 index 000000000..aaa83817c --- /dev/null +++ b/back/methods/vn-user/specs/share-token.spec.js @@ -0,0 +1,27 @@ +const {models} = require('vn-loopback/server/server'); +describe('Share Token', () => { + let ctx = null; + beforeAll(async() => { + const unAuthCtx = { + req: { + headers: {}, + connection: { + remoteAddress: '127.0.0.1' + }, + getLocale: () => 'en' + }, + args: {} + }; + let login = await models.VnUser.signIn(unAuthCtx, 'salesAssistant', 'nightmare'); + let accessToken = await models.AccessToken.findById(login.token); + ctx = {req: {accessToken: accessToken}}; + }); + + it('should renew token', async() => { + const multimediaToken = await models.VnUser.shareToken(ctx); + + expect(Object.keys(multimediaToken).length).toEqual(1); + expect(multimediaToken.multimediaToken.userId).toEqual(ctx.req.accessToken.userId); + expect(multimediaToken.multimediaToken.scopes[0]).toEqual('read:multimedia'); + }); +}); diff --git a/back/models/vn-user.js b/back/models/vn-user.js index 3a416d7e3..b59f13ffa 100644 --- a/back/models/vn-user.js +++ b/back/models/vn-user.js @@ -13,6 +13,7 @@ module.exports = function(Self) { require('../methods/vn-user/privileges')(Self); require('../methods/vn-user/validate-auth')(Self); require('../methods/vn-user/renew-token')(Self); + require('../methods/vn-user/share-token')(Self); require('../methods/vn-user/update-user')(Self); Self.definition.settings.acls = Self.definition.settings.acls.filter(acl => acl.property !== 'create'); diff --git a/back/models/vn-user.json b/back/models/vn-user.json index 639603643..5f6ac3f47 100644 --- a/back/models/vn-user.json +++ b/back/models/vn-user.json @@ -1,129 +1,140 @@ { - "name": "VnUser", - "base": "User", - "validateUpsert": true, - "options": { - "mysql": { - "table": "account.user" - } - }, + "name": "VnUser", + "base": "User", + "validateUpsert": true, + "options": { + "mysql": { + "table": "account.user" + } + }, "mixins": { "Loggable": true }, "resetPasswordTokenTTL": "604800", - "properties": { - "id": { - "type": "number", - "id": true - }, + "properties": { + "id": { + "type": "number", + "id": true + }, "name": { - "type": "string", - "required": true - }, - "username": { - "type": "string" - }, - "roleFk": { - "type": "number", - "mysql": { - "columnName": "role" - } - }, - "nickname": { - "type": "string" - }, - "lang": { - "type": "string" - }, - "active": { - "type": "boolean" - }, - "email": { - "type": "string" - }, - "emailVerified": { - "type": "boolean" - }, - "created": { - "type": "date" - }, - "updated": { - "type": "date" - }, - "image": { - "type": "string" - }, - "hasGrant": { - "type": "boolean" - }, + "type": "string", + "required": true + }, + "username": { + "type": "string" + }, + "roleFk": { + "type": "number", + "mysql": { + "columnName": "role" + } + }, + "nickname": { + "type": "string" + }, + "lang": { + "type": "string" + }, + "active": { + "type": "boolean" + }, + "email": { + "type": "string" + }, + "emailVerified": { + "type": "boolean" + }, + "created": { + "type": "date" + }, + "updated": { + "type": "date" + }, + "image": { + "type": "string" + }, + "hasGrant": { + "type": "boolean" + }, "passExpired": { "type": "date" }, - "twoFactor": { - "type": "string" - } - }, - "relations": { - "role": { - "type": "belongsTo", - "model": "VnRole", - "foreignKey": "roleFk" - }, - "roles": { - "type": "hasMany", - "model": "RoleRole", - "foreignKey": "role", - "primaryKey": "roleFk" - }, - "emailUser": { - "type": "hasOne", - "model": "EmailUser", - "foreignKey": "userFk" - }, - "worker": { - "type": "hasOne", - "model": "Worker", - "foreignKey": "id" - }, - "userConfig": { - "type": "hasOne", - "model": "UserConfig", - "foreignKey": "userFk" - } - }, - "acls": [ - { - "property": "signIn", - "accessType": "EXECUTE", - "principalType": "ROLE", - "principalId": "$everyone", - "permission": "ALLOW" - }, { - "property": "recoverPassword", - "accessType": "EXECUTE", - "principalType": "ROLE", - "principalId": "$everyone", - "permission": "ALLOW" - }, { - "property": "validateAuth", - "accessType": "EXECUTE", - "principalType": "ROLE", - "principalId": "$everyone", - "permission": "ALLOW" - }, { - "property": "privileges", - "accessType": "*", - "principalType": "ROLE", - "principalId": "$authenticated", - "permission": "ALLOW" - }, { - "property": "renewToken", - "accessType": "WRITE", - "principalType": "ROLE", - "principalId": "$authenticated", - "permission": "ALLOW" - } - ], + "twoFactor": { + "type": "string" + } + }, + "relations": { + "role": { + "type": "belongsTo", + "model": "VnRole", + "foreignKey": "roleFk" + }, + "roles": { + "type": "hasMany", + "model": "RoleRole", + "foreignKey": "role", + "primaryKey": "roleFk" + }, + "emailUser": { + "type": "hasOne", + "model": "EmailUser", + "foreignKey": "userFk" + }, + "worker": { + "type": "hasOne", + "model": "Worker", + "foreignKey": "id" + }, + "userConfig": { + "type": "hasOne", + "model": "UserConfig", + "foreignKey": "userFk" + } + }, + "acls": [ + { + "property": "signIn", + "accessType": "EXECUTE", + "principalType": "ROLE", + "principalId": "$everyone", + "permission": "ALLOW" + }, + { + "property": "recoverPassword", + "accessType": "EXECUTE", + "principalType": "ROLE", + "principalId": "$everyone", + "permission": "ALLOW" + }, + { + "property": "validateAuth", + "accessType": "EXECUTE", + "principalType": "ROLE", + "principalId": "$everyone", + "permission": "ALLOW" + }, + { + "property": "privileges", + "accessType": "*", + "principalType": "ROLE", + "principalId": "$authenticated", + "permission": "ALLOW" + }, + { + "property": "renewToken", + "accessType": "WRITE", + "principalType": "ROLE", + "principalId": "$authenticated", + "permission": "ALLOW" + }, + { + "property": "shareToken", + "accessType": "WRITE", + "principalType": "ROLE", + "principalId": "$authenticated", + "permission": "ALLOW" + } + ], "scopes": { "preview": { "fields": [ @@ -140,7 +151,7 @@ "hasGrant", "realm", "email", - "emailVerified" + "emailVerified" ] } } diff --git a/db/dump/fixtures.before.sql b/db/dump/fixtures.before.sql index 3cf9cd928..d4f5c51dd 100644 --- a/db/dump/fixtures.before.sql +++ b/db/dump/fixtures.before.sql @@ -592,13 +592,13 @@ INSERT INTO `vn`.`supplierAccount`(`id`, `supplierFk`, `iban`, `bankEntityFk`) VALUES (241, 442, 'ES111122333344111122221111', 128); -INSERT INTO `vn`.`company`(`id`, `code`, `supplierAccountFk`, `workerManagerFk`, `companyCode`, `sage200Company`, `expired`, `companyGroupFk`, `phytosanitary` , `clientFk`) +INSERT INTO `vn`.`company`(`id`, `code`, `supplierAccountFk`, `workerManagerFk`, `companyCode`, `expired`, `companyGroupFk`, `phytosanitary` , `clientFk`) VALUES - (69 , 'CCs', NULL, 30, NULL, 0, NULL, 1, NULL , NULL), - (442 , 'VNL', 241, 30, 2 , 1, NULL, 2, 'VNL Company - Plant passport' , 1101), - (567 , 'VNH', NULL, 30, NULL, 4, NULL, 1, 'VNH Company - Plant passport' , NULL), - (791 , 'FTH', NULL, 30, NULL, 3, '2015-11-30', 1, NULL , NULL), - (1381, 'ORN', NULL, 30, NULL, 7, NULL, 1, 'ORN Company - Plant passport' , NULL); + (69 , 'CCs', NULL, 30, 0, NULL, 1, NULL , NULL), + (442 , 'VNL', 241, 30, 1, NULL, 2, 'VNL Company - Plant passport' , 1101), + (567 , 'VNH', NULL, 30, 4, NULL, 1, 'VNH Company - Plant passport' , NULL), + (791 , 'FTH', NULL, 30, 3, '2015-11-30', 1, NULL , NULL), + (1381, 'ORN', NULL, 30, 7, NULL, 1, 'ORN Company - Plant passport' , NULL); INSERT INTO `vn`.`taxArea` (`code`, `claveOperacionFactura`, `CodigoTransaccion`) VALUES @@ -1492,8 +1492,8 @@ INSERT INTO `bs`.`waste`(`buyer`, `year`, `week`, `family`, `itemFk`, `itemTypeF INSERT INTO `vn`.`buy`(`id`,`entryFk`,`itemFk`,`buyingValue`,`quantity`,`packagingFk`,`stickers`,`freightValue`,`packageValue`,`comissionValue`,`packing`,`grouping`,`groupingMode`,`location`,`price1`,`price2`,`price3`, `printedStickers`,`isChecked`,`isIgnored`,`weight`, `created`) VALUES - (1, 1, 1, 50, 5000, 4, 1, 1.500, 1.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0, 1, 0, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -2 MONTH)), - (2, 2, 1, 50, 100, 4, 1, 1.500, 1.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0, 1, 0, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH)), + (1, 1, 1, 50, 5000, 4, 1, 1.500, 1.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0, 1, 0, 1, util.VN_CURDATE() - INTERVAL 2 MONTH), + (2, 2, 1, 50, 100, 4, 1, 1.500, 1.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0, 1, 0, 1, util.VN_CURDATE() - INTERVAL 1 MONTH), (3, 3, 1, 50, 100, 4, 1, 1.500, 1.500, 0.000, 1, 1, 0, NULL, 0.00, 99.6, 99.4, 0, 1, 0, 1, util.VN_CURDATE()), (4, 2, 2, 5, 450, 3, 1, 1.000, 1.000, 0.000, 10, 10, 0, NULL, 0.00, 7.30, 7.00, 0, 1, 0, 2.5, util.VN_CURDATE()), (5, 3, 3, 55, 500, 5, 1, 1.000, 1.000, 0.000, 1, 1, 0, NULL, 0.00, 78.3, 75.6, 0, 1, 0, 2.5, util.VN_CURDATE()), diff --git a/db/routines/sage/procedures/pgc_add.sql b/db/routines/sage/procedures/pgc_add.sql index ebcb2d043..78d80a9fe 100644 --- a/db/routines/sage/procedures/pgc_add.sql +++ b/db/routines/sage/procedures/pgc_add.sql @@ -17,13 +17,13 @@ BEGIN e.id accountFk, UCASE(e.name), '' - FROM expense e + FROM vn.expense e UNION SELECT company_getCode(vCompanyFk), a.account, UCASE(a.bank), '' - FROM accounting a + FROM vn.accounting a WHERE a.isActive AND a.`account` UNION diff --git a/db/routines/vn/functions/travel_hasUniqueAwb.sql b/db/routines/vn/functions/travel_hasUniqueAwb.sql new file mode 100644 index 000000000..e918f1a26 --- /dev/null +++ b/db/routines/vn/functions/travel_hasUniqueAwb.sql @@ -0,0 +1,28 @@ +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` FUNCTION `vn`.`travel_hasUniqueAwb`( + vSelf INT +) + RETURNS BOOL + READS SQL DATA +BEGIN +/** + * Comprueba que el travel pasado tiene un AWB lógico, + * no se pueden tener varios AWB asociados al mismo DUA + * + * @param vSelf Id del travel + */ + DECLARE vHasUniqueAwb BOOL DEFAULT TRUE; + + SELECT NOT COUNT(t2.awbFk) INTO vHasUniqueAwb + FROM entry e + JOIN travel t ON t.id = e.travelFk + JOIN duaEntry de ON de.entryFk = e.id + JOIN duaEntry de2 ON de2.duaFk = de.duaFk + JOIN entry e2 ON e2.id = de2.entryFk + JOIN travel t2 ON t2.id = e2.travelFk + WHERE t.id = vSelf + AND t2.awbFk <> t.awbFk; + + RETURN vHasUniqueAwb; +END$$ +DELIMITER ; diff --git a/db/routines/vn/triggers/entry_beforeInsert.sql b/db/routines/vn/triggers/entry_beforeInsert.sql index f475630db..c0c0aa28c 100644 --- a/db/routines/vn/triggers/entry_beforeInsert.sql +++ b/db/routines/vn/triggers/entry_beforeInsert.sql @@ -7,6 +7,8 @@ BEGIN CALL supplier_checkIsActive(NEW.supplierFk); SET NEW.currencyFk = entry_getCurrency(NEW.currencyFk, NEW.supplierFk); SET NEW.commission = entry_getCommission(NEW.travelFk, NEW.currencyFk,NEW.supplierFk); - + IF NEW.travelFk IS NOT NULL AND NOT travel_hasUniqueAwb(NEW.travelFk) THEN + CALL util.throw('The travel is incorrect, there is a different AWB in the associated entries'); + END IF; END$$ DELIMITER ; diff --git a/db/routines/vn/triggers/entry_beforeUpdate.sql b/db/routines/vn/triggers/entry_beforeUpdate.sql index 60b83002c..384feb458 100644 --- a/db/routines/vn/triggers/entry_beforeUpdate.sql +++ b/db/routines/vn/triggers/entry_beforeUpdate.sql @@ -8,13 +8,18 @@ BEGIN DECLARE vHasDistinctWarehouses BOOL; SET NEW.editorFk = account.myUser_getId(); + + IF NOT (NEW.travelFk <=> OLD.travelFk) THEN - IF !(NEW.travelFk <=> OLD.travelFk) THEN + IF NEW.travelFk IS NOT NULL AND NOT travel_hasUniqueAwb(NEW.travelFk) THEN + CALL util.throw('The travel is incorrect, there is a different AWB in the associated entries'); + END IF; + SELECT COUNT(*) > 0 INTO vIsVirtual FROM entryVirtual WHERE entryFk = NEW.id; - SELECT !(o.warehouseInFk <=> n.warehouseInFk) - OR !(o.warehouseOutFk <=> n.warehouseOutFk) + SELECT NOT (o.warehouseInFk <=> n.warehouseInFk) + OR NOT (o.warehouseOutFk <=> n.warehouseOutFk) INTO vHasDistinctWarehouses FROM travel o, travel n WHERE o.id = OLD.travelFk @@ -43,9 +48,8 @@ BEGIN SET NEW.currencyFk = entry_getCurrency(NEW.currencyFk, NEW.supplierFk); END IF; - IF NOT (NEW.travelFk <=> OLD.travelFk) - OR NOT (NEW.currencyFk <=> OLD.currencyFk) THEN - SET NEW.commission = entry_getCommission(NEW.travelFk, NEW.currencyFk,NEW.supplierFk); + IF NOT (NEW.travelFk <=> OLD.travelFk) OR NOT (NEW.currencyFk <=> OLD.currencyFk) THEN + SET NEW.commission = entry_getCommission(NEW.travelFk, NEW.currencyFk, NEW.supplierFk); END IF; END$$ DELIMITER ; diff --git a/db/routines/vn/triggers/travel_afterUpdate.sql b/db/routines/vn/triggers/travel_afterUpdate.sql index b4e40ae41..7752505e3 100644 --- a/db/routines/vn/triggers/travel_afterUpdate.sql +++ b/db/routines/vn/triggers/travel_afterUpdate.sql @@ -5,7 +5,7 @@ CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`travel_afterUpdate` BEGIN CALL stock.log_add('travel', NEW.id, OLD.id); - IF !(NEW.shipped <=> OLD.shipped) THEN + IF NOT(NEW.shipped <=> OLD.shipped) THEN UPDATE entry SET commission = entry_getCommission(travelFk, currencyFk,supplierFk) WHERE travelFk = NEW.id; @@ -23,5 +23,9 @@ BEGIN CALL buy_checkItem(); END IF; END IF; + + IF (NOT(NEW.awbFk <=> OLD.awbFk)) AND NEW.awbFk IS NOT NULL AND NOT travel_hasUniqueAwb(NEW.id) THEN + CALL util.throw('The AWB is incorrect, there is a different AWB in the associated entries'); + END IF; END$$ DELIMITER ; diff --git a/db/routines/vn/triggers/travel_beforeInsert.sql b/db/routines/vn/triggers/travel_beforeInsert.sql index 4e1dae3ef..817bd69bb 100644 --- a/db/routines/vn/triggers/travel_beforeInsert.sql +++ b/db/routines/vn/triggers/travel_beforeInsert.sql @@ -8,5 +8,9 @@ BEGIN CALL travel_checkDates(NEW.shipped, NEW.landed); CALL travel_checkWarehouseIsFeedStock(NEW.warehouseInFk); + + IF NEW.awbFk IS NOT NULL AND NOT travel_hasUniqueAwb(NEW.id) THEN + CALL util.throw('The AWB is incorrect, there is a different AWB in the associated entries'); + END IF; END$$ DELIMITER ; diff --git a/db/versions/10893-limeFern/00-sage.sql b/db/versions/10893-limeFern/00-sage.sql new file mode 100644 index 000000000..d4c7e6221 --- /dev/null +++ b/db/versions/10893-limeFern/00-sage.sql @@ -0,0 +1,73 @@ +-- Auto-generated SQL script #202403061303 +UPDATE vn.company + SET companyCode=0 + WHERE id=69; +UPDATE vn.company + SET companyCode=1 + WHERE id=442; +UPDATE vn.company + SET companyCode=4 + WHERE id=567; +UPDATE vn.company + SET companyCode=2 + WHERE id=791; +UPDATE vn.company + SET companyCode=3 + WHERE id=792; +UPDATE vn.company + SET companyCode=5 + WHERE id=965; +UPDATE vn.company + SET companyCode=7 + WHERE id=1381; +UPDATE vn.company + SET companyCode=3 + WHERE id=1463; +UPDATE vn.company + SET companyCode=8 + WHERE id=2142; +UPDATE vn.company + SET companyCode=6 + WHERE id=2393; +UPDATE vn.company + SET companyCode=9 + WHERE id=3869; + +-- Auto-generated SQL script #202403061311 +UPDATE vn.company + SET sage200Company=NULL + WHERE id=69; +UPDATE vn.company + SET sage200Company=NULL + WHERE id=442; +UPDATE vn.company + SET sage200Company=NULL + WHERE id=567; +UPDATE vn.company + SET sage200Company=NULL + WHERE id=791; +UPDATE vn.company + SET sage200Company=NULL + WHERE id=792; +UPDATE vn.company + SET sage200Company=NULL + WHERE id=965; +UPDATE vn.company + SET sage200Company=NULL + WHERE id=1381; +UPDATE vn.company + SET sage200Company=NULL + WHERE id=1463; +UPDATE vn.company + SET sage200Company=NULL + WHERE id=2142; +UPDATE vn.company + SET sage200Company=NULL + WHERE id=2393; +UPDATE vn.company + SET sage200Company=NULL + WHERE id=3869; + + +ALTER TABLE vn.company CHANGE sage200Company sage200Company__ int(2) DEFAULT NULL NULL COMMENT '@deprecated 06/03/2024'; +ALTER TABLE vn.company MODIFY COLUMN sage200Company__ int(2) DEFAULT NULL NULL COMMENT '@deprecated 06/03/2024'; diff --git a/db/versions/10919-brownMoss/00-firstScript.sql b/db/versions/10919-brownMoss/00-firstScript.sql new file mode 100644 index 000000000..640d2180a --- /dev/null +++ b/db/versions/10919-brownMoss/00-firstScript.sql @@ -0,0 +1,3 @@ +-- Place your SQL code here + + diff --git a/front/core/services/auth.js b/front/core/services/auth.js index 844a5145d..753bc3fba 100644 --- a/front/core/services/auth.js +++ b/front/core/services/auth.js @@ -83,22 +83,27 @@ export default class Auth { } onLoginOk(json, now, remember) { - this.vnToken.set(json.data.token, now, json.data.ttl, remember); - - return this.loadAcls().then(() => { - let continueHash = this.$state.params.continue; - if (continueHash) - this.$window.location = continueHash; - else - this.$state.go('home'); - }); + return this.$http.get('VnUsers/ShareToken', { + headers: {Authorization: json.data.token} + }).then(({data}) => { + this.vnToken.set(json.data.token, data.multimediaToken.id, now, json.data.ttl, remember); + this.loadAcls().then(() => { + let continueHash = this.$state.params.continue; + if (continueHash) + this.$window.location = continueHash; + else + this.$state.go('home'); + }); + }).catch(() => {}); } logout() { + this.$http.post('Accounts/logout', null, {headers: {'Authorization': this.vnToken.tokenMultimedia}, + }).catch(() => {}); + let promise = this.$http.post('VnUsers/logout', null, { headers: {Authorization: this.vnToken.token} }).catch(() => {}); - this.vnToken.unset(); this.loggedIn = false; this.vnModules.reset(); diff --git a/front/core/services/interceptor.js b/front/core/services/interceptor.js index 0c3253c69..90d813ed4 100644 --- a/front/core/services/interceptor.js +++ b/front/core/services/interceptor.js @@ -19,7 +19,7 @@ function interceptor($q, vnApp, $translate) { if (config.url.charAt(0) !== '/' && apiPath) config.url = `${apiPath}${config.url}`; - if (token) + if (token && !config.headers.Authorization) config.headers.Authorization = token; if ($translate.use()) config.headers['Accept-Language'] = $translate.use(); diff --git a/front/core/services/token.js b/front/core/services/token.js index c8cb4f6bb..125de6b9a 100644 --- a/front/core/services/token.js +++ b/front/core/services/token.js @@ -24,21 +24,22 @@ export default class Token { } catch (e) {} } - set(token, created, ttl, remember) { + set(token, tokenMultimedia, created, ttl, remember) { this.unset(); Object.assign(this, { token, + tokenMultimedia, created, ttl, remember }); - this.vnInterceptor.setToken(token); + this.vnInterceptor.setToken(token, tokenMultimedia); try { if (remember) - this.setStorage(localStorage, token, created, ttl); + this.setStorage(localStorage, token, tokenMultimedia, created, ttl); else - this.setStorage(sessionStorage, token, created, ttl); + this.setStorage(sessionStorage, token, tokenMultimedia, created, ttl); } catch (err) { console.error(err); } @@ -46,6 +47,7 @@ export default class Token { unset() { this.token = null; + this.tokenMultimedia = null; this.created = null; this.ttl = null; this.remember = null; @@ -57,13 +59,15 @@ export default class Token { getStorage(storage) { this.token = storage.getItem('vnToken'); + this.tokenMultimedia = storage.getItem('vnTokenMultimedia'); if (!this.token) return; const created = storage.getItem('vnTokenCreated'); this.created = created && new Date(created); this.ttl = storage.getItem('vnTokenTtl'); } - setStorage(storage, token, created, ttl) { + setStorage(storage, token, tokenMultimedia, created, ttl) { + storage.setItem('vnTokenMultimedia', tokenMultimedia); storage.setItem('vnToken', token); storage.setItem('vnTokenCreated', created.toJSON()); storage.setItem('vnTokenTtl', ttl); @@ -71,6 +75,7 @@ export default class Token { removeStorage(storage) { storage.removeItem('vnToken'); + storage.removeItem('vnTokenMultimedia'); storage.removeItem('vnTokenCreated'); storage.removeItem('vnTokenTtl'); } diff --git a/front/salix/components/layout/index.js b/front/salix/components/layout/index.js index 89912d4e3..e935c6d99 100644 --- a/front/salix/components/layout/index.js +++ b/front/salix/components/layout/index.js @@ -23,8 +23,7 @@ export class Layout extends Component { if (!this.$.$root.user) return; const userId = this.$.$root.user.id; - const token = this.vnToken.token; - return `/api/Images/user/160x160/${userId}/download?access_token=${token}`; + return `/api/Images/user/160x160/${userId}/download?access_token=${this.vnToken.tokenMultimedia}`; } refresh() { diff --git a/front/salix/components/log/index.html b/front/salix/components/log/index.html index c75030100..a3aaf0011 100644 --- a/front/salix/components/log/index.html +++ b/front/salix/components/log/index.html @@ -31,7 +31,7 @@ ng-click="$ctrl.showDescriptor($event, userLog)"> + ng-src="/api/Images/user/160x160/{{::userLog.userFk}}/download?access_token={{::$ctrl.vnToken.tokenMultimedia}}"> @@ -181,7 +181,7 @@ val="{{::nickname}}"> + ng-src="/api/Images/user/160x160/{{::id}}/download?access_token={{::$ctrl.vnToken.tokenMultimedia}}">
diff --git a/front/salix/module.js b/front/salix/module.js index 0ce855308..53b718427 100644 --- a/front/salix/module.js +++ b/front/salix/module.js @@ -13,7 +13,7 @@ export function run($window, $rootScope, vnAuth, vnApp, vnToken, $state) { if (!collection || !size || !id) return; const basePath = `/api/Images/${collection}/${size}/${id}`; - return `${basePath}/download?access_token=${vnToken.token}`; + return `${basePath}/download?access_token=${vnToken.tokenMultimedia}`; }; $window.validations = {}; diff --git a/loopback/locale/en.json b/loopback/locale/en.json index 53b1a8bb5..31b954a32 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -220,5 +220,7 @@ "Shelving not valid": "Shelving not valid", "printerNotExists": "The printer does not exist", "There are not picking tickets": "There are not picking tickets", - "ticketCommercial": "The ticket {{ ticket }} for the salesperson {{ salesMan }} is in preparation. (automatically generated message)" + "ticketCommercial": "The ticket {{ ticket }} for the salesperson {{ salesMan }} is in preparation. (automatically generated message)", + "This password can only be changed by the user themselves": "This password can only be changed by the user themselves", + "They're not your subordinate": "They're not your subordinate" } diff --git a/loopback/locale/es.json b/loopback/locale/es.json index 3748b6eaf..7730d4a8c 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -346,5 +346,7 @@ "CountryFK cannot be empty": "El país no puede estar vacío", "Cmr file does not exist": "El archivo del cmr no existe", "You are not allowed to modify the alias": "No estás autorizado a modificar el alias", - "The address of the customer must have information about Incoterms and Customs Agent": "El consignatario del cliente debe tener informado Incoterms y Agente de aduanas" -} \ No newline at end of file + "The address of the customer must have information about Incoterms and Customs Agent": "El consignatario del cliente debe tener informado Incoterms y Agente de aduanas", + "This password can only be changed by the user themselves": "Esta contraseña solo puede ser modificada por el propio usuario", + "They're not your subordinate": "No es tu subordinado/a." +} diff --git a/modules/account/back/methods/account/logout.js b/modules/account/back/methods/account/logout.js index 5db3efa33..7d2e8153e 100644 --- a/modules/account/back/methods/account/logout.js +++ b/modules/account/back/methods/account/logout.js @@ -15,7 +15,8 @@ module.exports = Self => { http: { path: `/logout`, verb: 'POST' - } + }, + accessScopes: ['DEFAULT', 'read:multimedia'] }); Self.logout = async ctx => Self.app.models.VnUser.logout(ctx.req.accessToken.id); diff --git a/modules/account/back/models/account.js b/modules/account/back/models/account.js index 5021a5d94..ceb26053c 100644 --- a/modules/account/back/models/account.js +++ b/modules/account/back/models/account.js @@ -1,4 +1,7 @@ +const ForbiddenError = require('vn-loopback/util/forbiddenError'); +const {models} = require('vn-loopback/server/server'); + module.exports = Self => { require('../methods/account/sync')(Self); require('../methods/account/sync-by-id')(Self); @@ -7,4 +10,11 @@ module.exports = Self => { require('../methods/account/logout')(Self); require('../methods/account/change-password')(Self); require('../methods/account/set-password')(Self); + + Self.setUnverifiedPassword = async(id, pass, options) => { + const {emailVerified} = await models.VnUser.findById(id, {fields: ['emailVerified']}, options); + if (emailVerified) throw new ForbiddenError('This password can only be changed by the user themselves'); + + await models.VnUser.setPassword(id, pass, options); + }; }; diff --git a/modules/claim/back/methods/claim/downloadFile.js b/modules/claim/back/methods/claim/downloadFile.js index 750356b0b..61784f39e 100644 --- a/modules/claim/back/methods/claim/downloadFile.js +++ b/modules/claim/back/methods/claim/downloadFile.js @@ -32,7 +32,8 @@ module.exports = Self => { http: { path: `/:id/downloadFile`, verb: 'GET' - } + }, + accessScopes: ['read:multimedia'] }); Self.downloadFile = async function(ctx, id) { diff --git a/modules/client/front/balance/index/index.html b/modules/client/front/balance/index/index.html index faf772c2d..34524d2f3 100644 --- a/modules/client/front/balance/index/index.html +++ b/modules/client/front/balance/index/index.html @@ -114,7 +114,7 @@ + href="api/InvoiceOuts/{{::balance.id}}/download?access_token={{::$ctrl.vnToken.tokenMultimedia}}"> diff --git a/modules/invoiceOut/back/methods/invoiceOut/download.js b/modules/invoiceOut/back/methods/invoiceOut/download.js index 4c76f7c07..cb71121d5 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/download.js +++ b/modules/invoiceOut/back/methods/invoiceOut/download.js @@ -31,7 +31,8 @@ module.exports = Self => { http: { path: '/:id/download', verb: 'GET' - } + }, + accessScopes: ['read:multimedia'] }); Self.download = async function(ctx, id, options) { diff --git a/modules/invoiceOut/back/methods/invoiceOut/downloadZip.js b/modules/invoiceOut/back/methods/invoiceOut/downloadZip.js index fe005f1ab..4f2a8aab3 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/downloadZip.js +++ b/modules/invoiceOut/back/methods/invoiceOut/downloadZip.js @@ -31,7 +31,8 @@ module.exports = Self => { http: { path: '/downloadZip', verb: 'GET' - } + }, + accessScopes: ['read:multimedia'] }); Self.downloadZip = async function(ctx, ids, options) { diff --git a/modules/invoiceOut/front/descriptor-menu/index.html b/modules/invoiceOut/front/descriptor-menu/index.html index face46293..1bf34831e 100644 --- a/modules/invoiceOut/front/descriptor-menu/index.html +++ b/modules/invoiceOut/front/descriptor-menu/index.html @@ -37,7 +37,7 @@ diff --git a/modules/invoiceOut/front/index/index.js b/modules/invoiceOut/front/index/index.js index 2cde3c940..403c51d58 100644 --- a/modules/invoiceOut/front/index/index.js +++ b/modules/invoiceOut/front/index/index.js @@ -25,7 +25,7 @@ export default class Controller extends Section { openPdf() { if (this.checked.length <= 1) { const [invoiceOutId] = this.checked; - const url = `api/InvoiceOuts/${invoiceOutId}/download?access_token=${this.vnToken.token}`; + const url = `api/InvoiceOuts/${invoiceOutId}/download?access_token=${this.vnToken.tokenMultimedia}`; window.open(url, '_blank'); } else { const invoiceOutIds = this.checked; diff --git a/modules/item/back/methods/item-image-queue/download.js b/modules/item/back/methods/item-image-queue/download.js index eb952daa4..e1bc248ae 100644 --- a/modules/item/back/methods/item-image-queue/download.js +++ b/modules/item/back/methods/item-image-queue/download.js @@ -11,6 +11,7 @@ module.exports = Self => { path: `/download`, verb: 'POST', }, + accessScopes: ['read:multimedia'] }); Self.download = async() => { diff --git a/modules/route/back/methods/route/downloadCmrsZip.js b/modules/route/back/methods/route/downloadCmrsZip.js index 58445f6f1..43f6e9648 100644 --- a/modules/route/back/methods/route/downloadCmrsZip.js +++ b/modules/route/back/methods/route/downloadCmrsZip.js @@ -29,7 +29,8 @@ module.exports = Self => { http: { path: '/downloadCmrsZip', verb: 'GET' - } + }, + accessScopes: ['read:multimedia'] }); Self.downloadCmrsZip = async function(ctx, ids, options) { diff --git a/modules/route/back/methods/route/downloadZip.js b/modules/route/back/methods/route/downloadZip.js index 597f1d1f6..d7fc30aa3 100644 --- a/modules/route/back/methods/route/downloadZip.js +++ b/modules/route/back/methods/route/downloadZip.js @@ -29,7 +29,8 @@ module.exports = Self => { http: { path: '/downloadZip', verb: 'GET' - } + }, + accessScopes: ['read:multimedia'] }); Self.downloadZip = async function(ctx, id, options) { diff --git a/modules/route/back/methods/route/driverRoutePdf.js b/modules/route/back/methods/route/driverRoutePdf.js index f0cd75f0e..e7b4dee17 100644 --- a/modules/route/back/methods/route/driverRoutePdf.js +++ b/modules/route/back/methods/route/driverRoutePdf.js @@ -34,7 +34,9 @@ module.exports = Self => { http: { path: '/:id/driver-route-pdf', verb: 'GET' - } + }, + accessScopes: ['read:multimedia'] + }); Self.driverRoutePdf = (ctx, id) => Self.printReport(ctx, id, 'driver-route'); diff --git a/modules/route/front/index/index.js b/modules/route/front/index/index.js index 7c19a26cd..0c5dfe7f3 100644 --- a/modules/route/front/index/index.js +++ b/modules/route/front/index/index.js @@ -40,7 +40,7 @@ export default class Controller extends Section { const stringRoutesIds = routesIds.join(','); if (this.checked.length <= 1) { - const url = `api/Routes/${stringRoutesIds}/driver-route-pdf?access_token=${this.vnToken.token}`; + const url = `api/Routes/${stringRoutesIds}/driver-route-pdf?access_token=${this.vnToken.tokenMultimedia}`; window.open(url, '_blank'); } else { const serializedParams = this.$httpParamSerializer({ diff --git a/modules/worker/back/methods/worker-dms/downloadFile.js b/modules/worker/back/methods/worker-dms/downloadFile.js index cc8653e0e..08fbcf924 100644 --- a/modules/worker/back/methods/worker-dms/downloadFile.js +++ b/modules/worker/back/methods/worker-dms/downloadFile.js @@ -29,7 +29,8 @@ module.exports = Self => { http: { path: `/:id/downloadFile`, verb: 'GET' - } + }, + accessScopes: ['read:multimedia'] }); Self.downloadFile = async function(ctx, id) { diff --git a/modules/worker/back/methods/worker/setPassword.js b/modules/worker/back/methods/worker/setPassword.js index 43d3d946f..9969530a4 100644 --- a/modules/worker/back/methods/worker/setPassword.js +++ b/modules/worker/back/methods/worker/setPassword.js @@ -1,31 +1,29 @@ -const UserError = require('vn-loopback/util/user-error'); +const ForbiddenError = require('vn-loopback/util/forbiddenError'); module.exports = Self => { Self.remoteMethodCtx('setPassword', { description: 'Set a new password', - accepts: [ - { - arg: 'workerFk', - type: 'number', - required: true, - description: 'The worker id', - }, - { - arg: 'newPass', - type: 'String', - required: true, - description: 'The new worker password' - } - ], + accepts: [{ + arg: 'id', + type: 'number', + required: true, + description: 'The worker id', + http: {source: 'path'} + }, { + arg: 'newPass', + type: 'String', + required: true, + description: 'The new worker password' + }], http: { path: `/:id/setPassword`, verb: 'PATCH' } }); - Self.setPassword = async(ctx, options) => { + Self.setPassword = async(ctx, id, newPass, options) => { const models = Self.app.models; const myOptions = {}; - const {args} = ctx; let tx; + if (typeof options == 'object') Object.assign(myOptions, options); if (!myOptions.transaction) { @@ -33,11 +31,10 @@ module.exports = Self => { myOptions.transaction = tx; } try { - const isSubordinate = await models.Worker.isSubordinate(ctx, args.workerFk, myOptions); - if (!isSubordinate) throw new UserError('You don\'t have enough privileges.'); + const isSubordinate = await Self.isSubordinate(ctx, id, myOptions); + if (!isSubordinate) throw new ForbiddenError('They\'re not your subordinate'); - await models.VnUser.setPassword(args.workerFk, args.newPass, myOptions); - await models.VnUser.updateAll({id: args.workerFk}, {emailVerified: true}, myOptions); + await models.Account.setUnverifiedPassword(id, newPass, myOptions); if (tx) await tx.commit(); } catch (e) { diff --git a/modules/worker/back/methods/worker/specs/setPassword.spec.js b/modules/worker/back/methods/worker/specs/setPassword.spec.js index fbb403b24..8d152bdd1 100644 --- a/modules/worker/back/methods/worker/specs/setPassword.spec.js +++ b/modules/worker/back/methods/worker/specs/setPassword.spec.js @@ -1,31 +1,30 @@ -const UserError = require('vn-loopback/util/user-error'); - -const models = require('vn-loopback/server/server').models; +const {models} = require('vn-loopback/server/server'); describe('worker setPassword()', () => { let ctx; + const newPass = 'H3rn4d3z#'; + const employeeId = 1; + const managerId = 20; + const administrativeId = 5; + beforeAll(() => { ctx = { req: { - accessToken: {}, + accessToken: {userId: managerId}, headers: {origin: 'http://localhost'} }, - args: {workerFk: 9} }; }); - beforeEach(() => { - ctx.req.accessToken.userId = 20; - ctx.args.newPass = 'H3rn4d3z#'; - }); - - it('should change the password', async() => { + it('should change the password if it is a subordinate and the email is not verified', async() => { const tx = await models.Worker.beginTransaction({}); try { const options = {transaction: tx}; - await models.Worker.setPassword(ctx, options); + await models.Worker.setPassword(ctx, employeeId, newPass, options); + const isNewPass = await passHasBeenChanged(employeeId, newPass, options); + expect(isNewPass).toBeTrue(); await tx.rollback(); } catch (e) { await tx.rollback(); @@ -33,29 +32,48 @@ describe('worker setPassword()', () => { } }); - it('should throw an error: Password does not meet requirements', async() => { - const tx = await models.Collection.beginTransaction({}); - ctx.args.newPass = 'Hi'; + it('should not change the password if it is a subordinate and the email is verified', async() => { + const tx = await models.Worker.beginTransaction({}); + try { const options = {transaction: tx}; - await models.Worker.setPassword(ctx, options); + await models.VnUser.updateAll({id: employeeId}, {emailVerified: true}, options); + await models.Worker.setPassword(ctx, employeeId, newPass, options); + + await tx.rollback(); + } catch (e) { + expect(e.message).toEqual(`This password can only be changed by the user themselves`); + await tx.rollback(); + } + }); + + it('should not change the password if it is not a subordinate', async() => { + const tx = await models.Worker.beginTransaction({}); + try { + const options = {transaction: tx}; + await models.Worker.setPassword(ctx, administrativeId, newPass, options); + await tx.rollback(); + } catch (e) { + expect(e.message).toEqual(`They're not your subordinate`); + await tx.rollback(); + } + }); + + it('should throw an error: Password does not meet requirements', async() => { + const tx = await models.Worker.beginTransaction({}); + const newPass = 'Hi'; + try { + const options = {transaction: tx}; + await models.Worker.setPassword(ctx, employeeId, newPass, options); await tx.rollback(); } catch (e) { expect(e.sqlMessage).toEqual('Password does not meet requirements'); await tx.rollback(); } }); - - it('should throw an error: You don\'t have enough privileges.', async() => { - ctx.req.accessToken.userId = 5; - const tx = await models.Collection.beginTransaction({}); - try { - const options = {transaction: tx}; - await models.Worker.setPassword(ctx, options); - await tx.rollback(); - } catch (e) { - expect(e).toEqual(new UserError(`You don't have enough privileges.`)); - await tx.rollback(); - } - }); }); + +const passHasBeenChanged = async(userId, pass, options) => { + const user = await models.VnUser.findById(userId, null, options); + return user.hasPassword(pass); +}; diff --git a/modules/worker/front/descriptor/index.html b/modules/worker/front/descriptor/index.html index 8290e2a15..73332efac 100644 --- a/modules/worker/front/descriptor/index.html +++ b/modules/worker/front/descriptor/index.html @@ -11,8 +11,8 @@ ? 'Click to allow the user to be disabled' : 'Click to exclude the user from getting disabled'}} - - Change password + + Change password diff --git a/modules/worker/front/descriptor/index.js b/modules/worker/front/descriptor/index.js index 13ffa6f2f..d7962369c 100644 --- a/modules/worker/front/descriptor/index.js +++ b/modules/worker/front/descriptor/index.js @@ -69,6 +69,7 @@ class Controller extends Descriptor { } ] }; + return this.getData(`Workers/${this.id}`, {filter}) .then(res => this.entity = res.data); } @@ -86,15 +87,14 @@ class Controller extends Descriptor { if (this.newPassword != this.repeatPassword) throw new UserError(`Passwords don't match`); this.$http.patch( - `Workers/${this.entity.id}/setPassword`, - {workerFk: this.entity.id, newPass: this.newPassword} + `Workers/${this.entity.id}/setPassword`, {newPass: this.newPassword} ) .then(() => { this.vnApp.showSuccess(this.$translate.instant('Password changed!')); - }); + }).then(() => this.loadData()); } } -Controller.$inject = ['$element', '$scope', '$rootScope']; +Controller.$inject = ['$element', '$scope', '$rootScope', 'vnConfig']; ngModule.vnComponent('vnWorkerDescriptor', { template: require('./index.html'), diff --git a/modules/worker/front/descriptor/index.spec.js b/modules/worker/front/descriptor/index.spec.js index d158a9e8e..4f7fa6a05 100644 --- a/modules/worker/front/descriptor/index.spec.js +++ b/modules/worker/front/descriptor/index.spec.js @@ -16,6 +16,7 @@ describe('vnWorkerDescriptor', () => { const id = 1; const response = 'foo'; + $httpBackend.whenGET('UserConfigs/getUserConfig').respond({}); $httpBackend.expectRoute('GET', `Workers/${id}`).respond(response); controller.id = id; $httpBackend.flush();