From 261fddb1cf0b2677413064040986a417049b4fea Mon Sep 17 00:00:00 2001 From: wbuezas Date: Tue, 1 Oct 2024 14:19:34 -0300 Subject: [PATCH] Renew token --- src/i18n/ca-ES/index.js | 7 +- src/i18n/en-US/index.js | 7 +- src/i18n/es-ES/index.js | 7 +- src/i18n/fr-FR/index.js | 8 +- src/i18n/pt-PT/index.js | 7 +- src/stores/user.js | 161 ++++++++++++++++++++++++++++++++++------ 6 files changed, 168 insertions(+), 29 deletions(-) diff --git a/src/i18n/ca-ES/index.js b/src/i18n/ca-ES/index.js index 0e033814..ae946728 100644 --- a/src/i18n/ca-ES/index.js +++ b/src/i18n/ca-ES/index.js @@ -161,5 +161,10 @@ export default { ErrCantWrite: "No s'ha pogut escriure el fitxer al disc", ErrExtension: "La pujada del fitxer s'ha aturat per una extensió", ErrDefault: 'Error de pujada desconegut', - 'Sync complete': 'Sincronització completa' + 'Sync complete': 'Sincronització completa', + // Errors + errors: { + statusUnauthorized: 'Accés denegat', + tokenConfig: 'Error al obtenir la configuració del token' + } }; diff --git a/src/i18n/en-US/index.js b/src/i18n/en-US/index.js index e3f546f8..4b478c83 100644 --- a/src/i18n/en-US/index.js +++ b/src/i18n/en-US/index.js @@ -194,5 +194,10 @@ export default { ErrCantWrite: 'Failed to write file to disk', ErrExtension: 'File upload stopped by extension', ErrDefault: 'Unknown upload error', - 'Sync complete': 'Synchronization complete' + 'Sync complete': 'Synchronization complete', + // Errors + errors: { + statusUnauthorized: 'Access denied', + tokenConfig: 'Error fetching token config' + } }; diff --git a/src/i18n/es-ES/index.js b/src/i18n/es-ES/index.js index d1fbdd7a..e9f609c8 100644 --- a/src/i18n/es-ES/index.js +++ b/src/i18n/es-ES/index.js @@ -194,5 +194,10 @@ export default { ErrCantWrite: 'Failed to write file to disk', ErrExtension: 'File upload stopped by extension', ErrDefault: 'Unknown upload error', - 'Sync complete': 'Sincronización completada' + 'Sync complete': 'Sincronización completada', + // Errors + errors: { + statusUnauthorized: 'Acceso denegado', + tokenConfig: 'Error al obtener configuración de token' + } }; diff --git a/src/i18n/fr-FR/index.js b/src/i18n/fr-FR/index.js index 6c04a77f..43462a49 100644 --- a/src/i18n/fr-FR/index.js +++ b/src/i18n/fr-FR/index.js @@ -165,5 +165,11 @@ export default { ErrCantWrite: "Échec de l'écriture du fichier sur le disque", ErrExtension: 'Téléchargement du fichier arrêté par extension', ErrDefault: 'Erreur de téléchargement inconnue', - 'Sync complete': 'Synchronisation terminée' + 'Sync complete': 'Synchronisation terminée', + // Errors + errors: { + statusUnauthorized: 'Accès refusé', + tokenConfig: + 'Erreur lors de la récupération de la configuration du jeton' + } }; diff --git a/src/i18n/pt-PT/index.js b/src/i18n/pt-PT/index.js index abaf4f49..018d54a8 100644 --- a/src/i18n/pt-PT/index.js +++ b/src/i18n/pt-PT/index.js @@ -158,5 +158,10 @@ export default { ErrCantWrite: 'Erro ao gravar arquivo no disco', ErrExtension: 'Erro de extensão do arquivo', ErrDefault: 'Erro desconhecido ao subir arquivo', - 'Sync complete': 'Sincronização completa' + 'Sync complete': 'Sincronização completa', + // Errors + errors: { + statusUnauthorized: 'Acesso negado', + tokenConfig: 'Erro ao obter configuração do token' + } }; diff --git a/src/stores/user.js b/src/stores/user.js index 5c347005..1e998ba2 100644 --- a/src/stores/user.js +++ b/src/stores/user.js @@ -9,12 +9,8 @@ const { notify } = useNotify(); export const useUserStore = defineStore('user', { state: () => { - const token = - sessionStorage.getItem('vnToken') || - localStorage.getItem('vnToken'); - return { - token, + token: '', isGuest: false, user: null, supplantedUser: null, @@ -24,7 +20,11 @@ export const useUserStore = defineStore('user', { { label: t('langs.ca'), lang: 'ca-ES', value: 'ca' }, { label: t('langs.fr'), lang: 'fr-FR', value: 'fr' }, { label: t('langs.pt'), lang: 'pt-PT', value: 'pt' } - ] + ], + keepLogin: false, + intervalId: null, + isCheckingToken: false, + tokenConfig: null }; }, @@ -46,10 +46,77 @@ export const useUserStore = defineStore('user', { this.updateSiteLocale(); }, - async getToken() { - this.token = + getToken() { + const token = sessionStorage.getItem('vnToken') || localStorage.getItem('vnToken'); + this.token = token; + return token; + }, + + setToken(token) { + const storage = this.keepLogin ? localStorage : sessionStorage; + storage.setItem('vnToken', token); + this.token = token; + }, + + async destroyToken(url, storage, key) { + if (storage.getItem(key)) { + try { + await api.post(url, null, { + headers: { Authorization: storage.getItem(key) } + }); + } catch (error) { + notify('errors.statusUnauthorized', 'negative'); + } finally { + storage.removeItem(key); + } + } + }, + + async destroy(destroyTokens = true) { + const tokens = { + tokenMultimedia: 'Accounts/logout', + token: 'VnUsers/logout' + }; + const storage = this.keepLogin ? localStorage : sessionStorage; + let destroyTokenPromises = []; + try { + if (destroyTokens) { + const { data: isValidToken } = await api.get( + 'VnUsers/validateToken' + ); + if (isValidToken) + destroyTokenPromises = Object.entries(tokens).map( + ([key, url]) => this.destroyToken(url, storage, key) + ); + } + } finally { + localStorage.clear(); + sessionStorage.clear(); + await Promise.allSettled(destroyTokenPromises); + this.user = null; + this.stopRenewer(); + } + }, + + setSession(data) { + const storage = this.keepLogin ? localStorage : sessionStorage; + storage.setItem('vnToken', data.token); + storage.setItem('created', data.created); + storage.setItem('ttl', data.ttl); + sessionStorage.setItem('keepLogin', this.keepLogin); + }, + + async login(user, password, remember) { + const params = { user, password }; + const { data } = await api.post('Accounts/login', params); + this.name = user; + this.keepLogin = remember; + this.setToken(data.token); + this.setSession({ created: Date.now(), ...data }); + await this.fetchTokenConfig(); + this.startInterval(); }, async tryAutoLogin() { @@ -67,22 +134,6 @@ export const useUserStore = defineStore('user', { return false; }, - async login(user, password, remember) { - const params = { user, password }; - const res = await api.post('Accounts/login', params); - - if (remember) { - localStorage.setItem('vnToken', res.data.token); - } else { - sessionStorage.setItem('vnToken', res.data.token); - } - - this.$patch({ - token: res.data.token, - name: user - }); - }, - async logout() { if (this.token != null) { try { @@ -96,6 +147,68 @@ export const useUserStore = defineStore('user', { useAppStore().onLogout(); }, + async fetchTokenConfig() { + try { + const { data } = await api.get('AccessTokenConfigs/findOne', { + filter: { fields: ['renewInterval', 'renewPeriod'] } + }); + if (!data) return; + this.tokenConfig = data; + sessionStorage.setItem('tokenConfig', data); + sessionStorage.setItem('renewPeriod', data.renewPeriod); + return data; + } catch (error) { + notify('errors.tokenConfig', 'negative'); + console.error('Error fetching token config:', error); + } + }, + + async renewToken() { + const _token = this.getToken(); + const token = await api.post('VnUsers/renewToken', { + headers: { Authorization: _token } + }); + + this.setToken(token.data.id); + }, + + async checkValidity() { + const tokenConfig = + this.tokenConfig ?? sessionStorage.getItem('tokenConfig'); + const storage = this.keepLogin ? localStorage : sessionStorage; + + const created = +storage.getItem('created'); + const ttl = +storage.getItem('ttl'); + if (this.isCheckingToken || !created) return; + this.isCheckingToken = true; + + const renewPeriodInSeconds = + Math.min(ttl, tokenConfig.value.renewPeriod) * 1000; + const maxDate = created + renewPeriodInSeconds; + const now = new Date().getTime(); + + if (isNaN(renewPeriodInSeconds) || now <= maxDate) { + return (this.isCheckingToken = false); + } + + await this.renewToken(); + this.isCheckingToken = false; + }, + + stopRenewer() { + clearInterval(this.intervalId); + }, + + startInterval() { + this.stopRenewer(); + const renewPeriod = +sessionStorage.getItem('renewPeriod'); + if (!renewPeriod) return; + this.intervalId = setInterval( + () => this.checkValidity(), + renewPeriod * 1000 + ); + }, + async fetchUser(userType = 'user') { try { const userData = await jApi.getObject(