refs #5554 fix: solo se llama al back cuando de va a renovar, i no se solapan las llamadas
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Vicent Llopis 2023-06-29 10:45:02 +02:00
parent bc8849d2b8
commit ac83788f1a
9 changed files with 132 additions and 140 deletions

View File

@ -17,23 +17,22 @@ module.exports = Self => {
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 token = ctx.req.accessToken;
const now = new Date();
const differenceMilliseconds = now - created;
const differenceMilliseconds = now - token.created;
const differenceSeconds = Math.floor(differenceMilliseconds / 1000);
const accessTokenConfig = await models.AccessTokenConfig.findOne({fields: ['renewPeriod']});
const fields = ['renewPeriod', 'courtesyTime'];
const accessTokenConfig = await models.AccessTokenConfig.findOne({fields});
if (differenceSeconds < accessTokenConfig.renewPeriod)
throw new UserError(`The renew period has not been exceeded`);
if (differenceSeconds < accessTokenConfig.renewPeriod - accessTokenConfig.courtesyTime)
throw new UserError(`The renew period has not been exceeded`, 'periodNotExceeded');
await Self.logout(tokenId);
const user = await Self.findById(userId);
await Self.logout(token.id);
const user = await Self.findById(token.userId);
const accessToken = await user.createAccessToken();
return {token: accessToken.id};
return {id: accessToken.id, ttl: accessToken.ttl};
};
};

View File

@ -16,6 +16,10 @@
"type": "number",
"required": true
},
"courtesyTime": {
"type": "number",
"required": true
},
"renewInterval": {
"type": "number",
"required": true

View File

@ -1,10 +1,11 @@
CREATE TABLE `salix`.`accessTokenConfig` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`renewPeriod` int(10) unsigned DEFAULT NULL,
`courtesyTime` 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`)
INSERT IGNORE INTO `salix`.`accessTokenConfig` (`id`, `renewPeriod`, `courtesyTime`, `renewInterval`)
VALUES
(1, 21600, 300);
(1, 21600, 5, 300);

View File

@ -1,11 +1,15 @@
import ngModule from '../module';
import HttpError from 'core/lib/http-error';
interceptor.$inject = ['$q', 'vnApp', 'vnToken', '$translate'];
function interceptor($q, vnApp, vnToken, $translate) {
interceptor.$inject = ['$q', 'vnApp', '$translate'];
function interceptor($q, vnApp, $translate) {
let apiPath = 'api/';
let token;
return {
setToken(newToken) {
token = newToken;
},
setApiPath(path) {
apiPath = path;
},
@ -14,8 +18,8 @@ function interceptor($q, vnApp, vnToken, $translate) {
if (config.url.charAt(0) !== '/' && apiPath)
config.url = `${apiPath}${config.url}`;
if (vnToken.token)
config.headers.Authorization = vnToken.token;
if (token)
config.headers.Authorization = token;
if ($translate.use())
config.headers['Accept-Language'] = $translate.use();
if (config.filter) {

View File

@ -6,43 +6,114 @@ import ngModule from '../module';
* @property {String} token The current login token or %null
*/
export default class Token {
constructor() {
try {
this.token = sessionStorage.getItem('vnToken');
this.created = sessionStorage.getItem('vnTokenCreated');
this.renewPeriod = sessionStorage.getItem('vnTokenRenewPeriod');
if (!this.token) {
this.token = localStorage.getItem('vnToken');
this.created = localStorage.getItem('vnTokenCreated');
this.renewPeriod = localStorage.getItem('vnTokenRenewPeriod');
}
} catch (e) {}
}
set(token, created, renewPeriod, remember) {
this.unset();
try {
if (remember) {
localStorage.setItem('vnToken', token);
localStorage.setItem('vnTokenCreated', created);
localStorage.setItem('vnTokenRenewPeriod', renewPeriod);
} else {
sessionStorage.setItem('vnToken', token);
sessionStorage.setItem('vnTokenCreated', created);
sessionStorage.setItem('vnTokenRenewPeriod', renewPeriod);
}
} catch (e) {}
constructor(vnInterceptor, $http, $rootScope) {
Object.assign(this, {
vnInterceptor,
$http,
$rootScope
});
this.token = token;
this.created = created;
this.renewPeriod = renewPeriod;
try {
this.getStorage(sessionStorage);
this.remember = true;
if (!this.token) {
this.getStorage(localStorage);
this.remember = false;
}
} catch (e) {}
}
set(token, created, ttl, remember) {
this.unset();
Object.assign(this, {
token,
created,
ttl,
remember
});
this.vnInterceptor.setToken(token);
try {
if (remember)
this.setStorage(localStorage, token, created, ttl);
else
this.setStorage(sessionStorage, token, created, ttl);
} catch (e) {}
}
unset() {
localStorage.removeItem('vnToken');
sessionStorage.removeItem('vnToken');
this.token = null;
this.created = null;
this.renewPeriod = null;
this.ttl = null;
this.remember = null;
this.vnInterceptor.setToken(null);
this.removeStorage(localStorage);
this.removeStorage(sessionStorage);
}
getStorage(storage) {
this.token = storage.getItem('vnToken');
this.created = storage.getItem('vnTokenCreated');
this.renewPeriod = storage.getItem('vnTokenRenewPeriod');
}
setStorage(storage, token, created, ttl) {
storage.setItem('vnToken', token);
storage.setItem('vnTokenCreated', created);
storage.setItem('vnTokenTtl', ttl);
}
removeStorage(storage) {
storage.removeItem('vnToken');
storage.removeItem('vnTokenCreated');
storage.removeItem('vnTokenTtl');
}
fetchConfig() {
const filter = {fields: ['renewInterval', 'renewPeriod']};
this.$http.get('AccessTokenConfigs/findOne', {filter}).then(res => {
const data = res.data;
if (!data) return;
this.renewPeriod = data.renewPeriod;
this.stopRenewer();
this.inservalId = setInterval(() => this.checkValidity(), data.renewInterval * 1000);
this.checkValidity();
});
}
checkValidity() {
if (this.checking) return;
this.checking = true;
const renewPeriod = Math.min(this.ttl, this.renewPeriod) * 1000;
const maxDate = this.created.getTime() + renewPeriod;
const now = new Date();
if (now.getTime() <= maxDate) {
this.checking = false;
return;
}
this.$http.post('VnUsers/renewToken')
.then(res => {
const token = res.data;
this.set(token.id, now, token.ttl, this.remember);
})
.catch(res => {
if (res.data?.error?.code !== 'periodNotExceeded')
throw res;
})
.finally(() => {
this.checking = false;
});
}
stopRenewer() {
clearInterval(this.inservalId);
}
}
Token.$inject = ['vnInterceptor', '$http', '$rootScope'];
ngModule.service('vnToken', Token);

View File

@ -42,7 +42,7 @@
<button class="buttonAccount">
<img
id="user"
ng-src="{{$ctrl.getImageUrl()}}"
ng-src="{{::$ctrl.getImageUrl()}}"
ng-click="userPopover.show($event)"
translate-attr="{title: 'Account'}"
on-error-src/>
@ -93,4 +93,4 @@
</vn-list>
</vn-portal>
<ui-view class="main-view"></ui-view>
<vn-scroll-up></vn-scroll-up>
<vn-scroll-up></vn-scroll-up>

View File

@ -10,7 +10,6 @@ export class Layout extends Component {
$onInit() {
this.getUserData();
this.getAccessTokenConfig();
}
getUserData() {
@ -32,53 +31,11 @@ export class Layout extends Component {
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(), intervalMilliseconds);
});
}
checkTokenValidity() {
this.vnToken.renewPeriod = this.vnToken.renewPeriod < this.renewPeriod
? this.vnToken.renewPeriod
: this.renewPeriod;
const secondsToRenew = this.vnToken.renewPeriod;
const milisecondsToRenew = secondsToRenew * 1000;
const created = new Date(this.vnToken.created);
const createdInMiliseconds = created.getTime();
const maxDateInMiliseconds = createdInMiliseconds + milisecondsToRenew;
const maxDate = new Date(maxDateInMiliseconds);
const now = new Date();
if (now > maxDate) {
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, now, this.vnToken.renewPeriod, remember);
}
})
.catch(err => {
if (err.status !== 400)
throw err;
});
}
}
$onDestroy() {
clearInterval(this.inservalId);
this.vnToken.stopRenewer();
}
}
Layout.$inject = ['$element', '$scope', 'vnModules', 'vnToken'];
Layout.$inject = ['$element', '$scope', 'vnModules'];
ngModule.vnComponent('vnLayout', {
template: require('./index.html'),

View File

@ -37,49 +37,4 @@ 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

@ -11,7 +11,8 @@ function config($stateProvider, $urlRouterProvider) {
abstract: true,
template: '<vn-layout></vn-layout>',
resolve: {
config: ['vnConfig', vnConfig => vnConfig.initialize()]
config: ['vnConfig', vnConfig => vnConfig.initialize()],
token: ['vnToken', vnToken => vnToken.fetchConfig()]
}
})
.state('outLayout', {