Merge pull request '5554-renovar_token' (!1566) from 5554-renovar_token into dev
gitea/salix/pipeline/head This commit looks good
Details
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:
commit
3462e49d23
|
@ -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};
|
||||||
|
};
|
||||||
|
};
|
|
@ -76,6 +76,6 @@ module.exports = Self => {
|
||||||
|
|
||||||
let loginInfo = Object.assign({password}, userInfo);
|
let loginInfo = Object.assign({password}, userInfo);
|
||||||
token = await Self.login(loginInfo, 'user');
|
token = await Self.login(loginInfo, 'user');
|
||||||
return {token: token.id};
|
return {token: token.id, created: token.created};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,7 +9,7 @@ describe('VnUser signIn()', () => {
|
||||||
|
|
||||||
expect(login.token).toBeDefined();
|
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() => {
|
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();
|
expect(login.token).toBeDefined();
|
||||||
|
|
||||||
await models.VnUser.signOut(ctx);
|
await models.VnUser.logout(ctx.req.accessToken.id);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -2,6 +2,14 @@
|
||||||
"AccountingType": {
|
"AccountingType": {
|
||||||
"dataSource": "vn"
|
"dataSource": "vn"
|
||||||
},
|
},
|
||||||
|
"AccessTokenConfig": {
|
||||||
|
"dataSource": "vn",
|
||||||
|
"options": {
|
||||||
|
"mysql": {
|
||||||
|
"table": "salix.accessTokenConfig"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Bank": {
|
"Bank": {
|
||||||
"dataSource": "vn"
|
"dataSource": "vn"
|
||||||
},
|
},
|
||||||
|
|
|
@ -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"
|
||||||
|
}]
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ module.exports = function(Self) {
|
||||||
require('../methods/vn-user/recover-password')(Self);
|
require('../methods/vn-user/recover-password')(Self);
|
||||||
require('../methods/vn-user/validate-token')(Self);
|
require('../methods/vn-user/validate-token')(Self);
|
||||||
require('../methods/vn-user/privileges')(Self);
|
require('../methods/vn-user/privileges')(Self);
|
||||||
|
require('../methods/vn-user/renew-token')(Self);
|
||||||
|
|
||||||
// Validations
|
// Validations
|
||||||
|
|
||||||
|
|
|
@ -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);
|
|
@ -2895,6 +2895,10 @@ INSERT INTO `vn`.`wagonTypeTray` (`id`, `typeFk`, `height`, `colorFk`)
|
||||||
(2, 1, 50, 2),
|
(2, 1, 50, 2),
|
||||||
(3, 1, 0, 3);
|
(3, 1, 0, 3);
|
||||||
|
|
||||||
|
INSERT INTO `salix`.`accessTokenConfig` (`id`, `renewPeriod`, `renewInterval`)
|
||||||
|
VALUES
|
||||||
|
(1, 21600, 300);
|
||||||
|
|
||||||
INSERT INTO `vn`.`travelConfig` (`id`, `warehouseInFk`, `warehouseOutFk`, `agencyFk`, `companyFk`)
|
INSERT INTO `vn`.`travelConfig` (`id`, `warehouseInFk`, `warehouseOutFk`, `agencyFk`, `companyFk`)
|
||||||
VALUES
|
VALUES
|
||||||
(1, 1, 1, 1, 442);
|
(1, 1, 1, 1, 442);
|
||||||
|
|
|
@ -64,7 +64,7 @@ export default class Auth {
|
||||||
}
|
}
|
||||||
|
|
||||||
onLoginOk(json, remember) {
|
onLoginOk(json, remember) {
|
||||||
this.vnToken.set(json.data.token, remember);
|
this.vnToken.set(json.data.token, json.data.created, remember);
|
||||||
|
|
||||||
return this.loadAcls().then(() => {
|
return this.loadAcls().then(() => {
|
||||||
let continueHash = this.$state.params.continue;
|
let continueHash = this.$state.params.continue;
|
||||||
|
|
|
@ -11,3 +11,4 @@ import './report';
|
||||||
import './email';
|
import './email';
|
||||||
import './file';
|
import './file';
|
||||||
import './date';
|
import './date';
|
||||||
|
|
||||||
|
|
|
@ -9,25 +9,33 @@ export default class Token {
|
||||||
constructor() {
|
constructor() {
|
||||||
try {
|
try {
|
||||||
this.token = sessionStorage.getItem('vnToken');
|
this.token = sessionStorage.getItem('vnToken');
|
||||||
if (!this.token)
|
this.created = sessionStorage.getItem('vnTokenCreated');
|
||||||
|
if (!this.token) {
|
||||||
this.token = localStorage.getItem('vnToken');
|
this.token = localStorage.getItem('vnToken');
|
||||||
|
this.created = localStorage.getItem('vnTokenCreated');
|
||||||
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
set(value, remember) {
|
set(token, created, remember) {
|
||||||
this.unset();
|
this.unset();
|
||||||
try {
|
try {
|
||||||
if (remember)
|
if (remember) {
|
||||||
localStorage.setItem('vnToken', value);
|
localStorage.setItem('vnToken', token);
|
||||||
else
|
localStorage.setItem('vnTokenCreated', created);
|
||||||
sessionStorage.setItem('vnToken', value);
|
} else {
|
||||||
|
sessionStorage.setItem('vnToken', token);
|
||||||
|
sessionStorage.setItem('vnTokenCreated', created);
|
||||||
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
this.token = value;
|
this.token = token;
|
||||||
|
this.created = created;
|
||||||
}
|
}
|
||||||
unset() {
|
unset() {
|
||||||
localStorage.removeItem('vnToken');
|
localStorage.removeItem('vnToken');
|
||||||
sessionStorage.removeItem('vnToken');
|
sessionStorage.removeItem('vnToken');
|
||||||
this.token = null;
|
this.token = null;
|
||||||
|
this.created = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,13 +3,14 @@ import Component from 'core/lib/component';
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
|
|
||||||
export class Layout extends Component {
|
export class Layout extends Component {
|
||||||
constructor($element, $, vnModules) {
|
constructor($element, $, vnModules, vnToken) {
|
||||||
super($element, $);
|
super($element, $);
|
||||||
this.modules = vnModules.get();
|
this.modules = vnModules.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
$onInit() {
|
$onInit() {
|
||||||
this.getUserData();
|
this.getUserData();
|
||||||
|
this.getAccessTokenConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
getUserData() {
|
getUserData() {
|
||||||
|
@ -30,8 +31,42 @@ export class Layout extends Component {
|
||||||
refresh() {
|
refresh() {
|
||||||
window.location.reload();
|
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', {
|
ngModule.vnComponent('vnLayout', {
|
||||||
template: require('./index.html'),
|
template: require('./index.html'),
|
||||||
|
|
|
@ -37,4 +37,49 @@ describe('Component vnLayout', () => {
|
||||||
expect(url).not.toBeDefined();
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -293,5 +293,6 @@
|
||||||
"Pass expired": "La contraseña ha caducado, cambiela desde Salix",
|
"Pass expired": "La contraseña ha caducado, cambiela desde Salix",
|
||||||
"Invalid NIF for VIES": "Invalid NIF for VIES",
|
"Invalid NIF for VIES": "Invalid NIF for VIES",
|
||||||
"Ticket does not exist": "Este ticket no existe",
|
"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"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue