Merge pull request '5554-renovar_token' (!1566) from 5554-renovar_token into dev
gitea/salix/pipeline/head This commit looks good Details

Reviewed-on: #1566
Reviewed-by: Juan Ferrer <juan@verdnatura.es>
Reviewed-by: Alex Moreno <alexm@verdnatura.es>
This commit is contained in:
Vicent Llopis 2023-06-20 12:38:46 +00:00
commit 3462e49d23
15 changed files with 195 additions and 56 deletions

View File

@ -0,0 +1,38 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('renewToken', {
description: 'Checks if the token has more than renewPeriod seconds to live and if so, renews it',
accepts: [],
returns: {
type: 'Object',
root: true
},
http: {
path: `/renewToken`,
verb: 'POST'
}
});
Self.renewToken = async function(ctx) {
const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
const created = ctx.req.accessToken.created;
const tokenId = ctx.req.accessToken.id;
const now = new Date();
const differenceMilliseconds = now - created;
const differenceSeconds = Math.floor(differenceMilliseconds / 1000);
const accessTokenConfig = await models.AccessTokenConfig.findOne({fields: ['renewPeriod']});
if (differenceSeconds <= accessTokenConfig.renewPeriod)
throw new UserError(`The renew period has not been exceeded`);
await Self.logout(tokenId);
const user = await Self.findById(userId);
const accessToken = await user.createAccessToken();
return {token: accessToken.id, created: accessToken.created};
};
};

View File

@ -76,6 +76,6 @@ module.exports = Self => {
let loginInfo = Object.assign({password}, userInfo);
token = await Self.login(loginInfo, 'user');
return {token: token.id};
return {token: token.id, created: token.created};
};
};

View File

@ -9,7 +9,7 @@ describe('VnUser signIn()', () => {
expect(login.token).toBeDefined();
await models.VnUser.signOut(ctx);
await models.VnUser.logout(ctx.req.accessToken.id);
});
it('should return the token if the user doesnt exist but the client does', async() => {
@ -19,7 +19,7 @@ describe('VnUser signIn()', () => {
expect(login.token).toBeDefined();
await models.VnUser.signOut(ctx);
await models.VnUser.logout(ctx.req.accessToken.id);
});
});

View File

@ -1,42 +0,0 @@
const {models} = require('vn-loopback/server/server');
describe('VnUser signOut()', () => {
it('should logout and remove token after valid login', async() => {
let loginResponse = await models.VnUser.signOut('buyer', 'nightmare');
let accessToken = await models.AccessToken.findById(loginResponse.token);
let ctx = {req: {accessToken: accessToken}};
let logoutResponse = await models.VnUser.signOut(ctx);
let tokenAfterLogout = await models.AccessToken.findById(loginResponse.token);
expect(logoutResponse).toBeTrue();
expect(tokenAfterLogout).toBeNull();
});
it('should throw a 401 error when token is invalid', async() => {
let error;
let ctx = {req: {accessToken: {id: 'invalidToken'}}};
try {
response = await models.VnUser.signOut(ctx);
} catch (e) {
error = e;
}
expect(error).toBeDefined();
expect(error.statusCode).toBe(401);
});
it('should throw an error when no token is passed', async() => {
let error;
let ctx = {req: {accessToken: null}};
try {
response = await models.VnUser.signOut(ctx);
} catch (e) {
error = e;
}
expect(error).toBeDefined();
});
});

View File

@ -2,6 +2,14 @@
"AccountingType": {
"dataSource": "vn"
},
"AccessTokenConfig": {
"dataSource": "vn",
"options": {
"mysql": {
"table": "salix.accessTokenConfig"
}
}
},
"Bank": {
"dataSource": "vn"
},

View File

@ -0,0 +1,30 @@
{
"name": "AccessTokenConfig",
"base": "VnModel",
"options": {
"mysql": {
"table": "accessTokenConfig"
}
},
"properties": {
"id": {
"type": "number",
"id": true,
"description": "Identifier"
},
"renewPeriod": {
"type": "number",
"required": true
},
"renewInterval": {
"type": "number",
"required": true
}
},
"acls": [{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}]
}

View File

@ -10,6 +10,7 @@ module.exports = function(Self) {
require('../methods/vn-user/recover-password')(Self);
require('../methods/vn-user/validate-token')(Self);
require('../methods/vn-user/privileges')(Self);
require('../methods/vn-user/renew-token')(Self);
// Validations

View File

@ -0,0 +1,10 @@
CREATE TABLE `salix`.`accessTokenConfig` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`renewPeriod` int(10) unsigned DEFAULT NULL,
`renewInterval` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
INSERT IGNORE INTO `salix`.`accessTokenConfig` (`id`, `renewPeriod`, `renewInterval`)
VALUES
(1, 21600, 300);

View File

@ -2895,6 +2895,10 @@ INSERT INTO `vn`.`wagonTypeTray` (`id`, `typeFk`, `height`, `colorFk`)
(2, 1, 50, 2),
(3, 1, 0, 3);
INSERT INTO `salix`.`accessTokenConfig` (`id`, `renewPeriod`, `renewInterval`)
VALUES
(1, 21600, 300);
INSERT INTO `vn`.`travelConfig` (`id`, `warehouseInFk`, `warehouseOutFk`, `agencyFk`, `companyFk`)
VALUES
(1, 1, 1, 1, 442);

View File

@ -64,7 +64,7 @@ export default class Auth {
}
onLoginOk(json, remember) {
this.vnToken.set(json.data.token, remember);
this.vnToken.set(json.data.token, json.data.created, remember);
return this.loadAcls().then(() => {
let continueHash = this.$state.params.continue;

View File

@ -11,3 +11,4 @@ import './report';
import './email';
import './file';
import './date';

View File

@ -9,25 +9,33 @@ export default class Token {
constructor() {
try {
this.token = sessionStorage.getItem('vnToken');
if (!this.token)
this.created = sessionStorage.getItem('vnTokenCreated');
if (!this.token) {
this.token = localStorage.getItem('vnToken');
this.created = localStorage.getItem('vnTokenCreated');
}
} catch (e) {}
}
set(value, remember) {
set(token, created, remember) {
this.unset();
try {
if (remember)
localStorage.setItem('vnToken', value);
else
sessionStorage.setItem('vnToken', value);
if (remember) {
localStorage.setItem('vnToken', token);
localStorage.setItem('vnTokenCreated', created);
} else {
sessionStorage.setItem('vnToken', token);
sessionStorage.setItem('vnTokenCreated', created);
}
} catch (e) {}
this.token = value;
this.token = token;
this.created = created;
}
unset() {
localStorage.removeItem('vnToken');
sessionStorage.removeItem('vnToken');
this.token = null;
this.created = null;
}
}

View File

@ -3,13 +3,14 @@ import Component from 'core/lib/component';
import './style.scss';
export class Layout extends Component {
constructor($element, $, vnModules) {
constructor($element, $, vnModules, vnToken) {
super($element, $);
this.modules = vnModules.get();
}
$onInit() {
this.getUserData();
this.getAccessTokenConfig();
}
getUserData() {
@ -30,8 +31,42 @@ export class Layout extends Component {
refresh() {
window.location.reload();
}
getAccessTokenConfig() {
this.$http.get('AccessTokenConfigs').then(json => {
const firtsResult = json.data[0];
if (!firtsResult) return;
this.renewPeriod = firtsResult.renewPeriod;
this.renewInterval = firtsResult.renewInterval;
const intervalMilliseconds = firtsResult.renewInterval * 1000;
this.inservalId = setInterval(this.checkTokenValidity.bind(this), intervalMilliseconds);
});
}
checkTokenValidity() {
const now = new Date();
const differenceMilliseconds = now - new Date(this.vnToken.created);
const differenceSeconds = Math.floor(differenceMilliseconds / 1000);
if (differenceSeconds > this.renewPeriod) {
this.$http.post('VnUsers/renewToken')
.then(json => {
if (json.data.token) {
let remember = true;
if (window.sessionStorage.vnToken) remember = false;
this.vnToken.set(json.data.token, json.data.created, remember);
}
});
}
}
$onDestroy() {
clearInterval(this.inservalId);
}
}
Layout.$inject = ['$element', '$scope', 'vnModules'];
Layout.$inject = ['$element', '$scope', 'vnModules', 'vnToken'];
ngModule.vnComponent('vnLayout', {
template: require('./index.html'),

View File

@ -37,4 +37,49 @@ describe('Component vnLayout', () => {
expect(url).not.toBeDefined();
});
});
describe('getAccessTokenConfig()', () => {
it(`should set the renewPeriod and renewInterval properties in localStorage`, () => {
const response = [{
renewPeriod: 100,
renewInterval: 5
}];
$httpBackend.expect('GET', `AccessTokenConfigs`).respond(response);
controller.getAccessTokenConfig();
$httpBackend.flush();
expect(controller.renewPeriod).toBe(100);
expect(controller.renewInterval).toBe(5);
expect(controller.inservalId).toBeDefined();
});
});
describe('checkTokenValidity()', () => {
it(`should not call renewToken and not set vnToken in the controller`, () => {
controller.renewPeriod = 100;
controller.vnToken.created = new Date();
controller.checkTokenValidity();
expect(controller.vnToken.token).toBeNull();
});
it(`should call renewToken and set vnToken properties in the controller`, () => {
const response = {
token: 999,
created: new Date()
};
controller.renewPeriod = 100;
const oneHourBefore = new Date(Date.now() - (60 * 60 * 1000));
controller.vnToken.created = oneHourBefore;
$httpBackend.expect('POST', `VnUsers/renewToken`).respond(response);
controller.checkTokenValidity();
$httpBackend.flush();
expect(controller.vnToken.token).toBe(999);
expect(controller.vnToken.created).toEqual(response.created);
});
});
});

View File

@ -293,5 +293,6 @@
"Pass expired": "La contraseña ha caducado, cambiela desde Salix",
"Invalid NIF for VIES": "Invalid NIF for VIES",
"Ticket does not exist": "Este ticket no existe",
"Ticket is already signed": "Este ticket ya ha sido firmado"
"Ticket is already signed": "Este ticket ya ha sido firmado",
"The renew period has not been exceeded": "El periodo de renovación no ha sido superado"
}