0
0
Fork 0

Merge pull request '#7190 - Renew tokenMultimedia' (!278) from 7190_renewTokenMultimedia into dev

Reviewed-on: verdnatura/salix-front#278
Reviewed-by: Alex Moreno <alexm@verdnatura.es>
This commit is contained in:
Javier Segarra 2024-05-02 08:11:08 +00:00
commit 6482f5680d
9 changed files with 299 additions and 66 deletions

View File

@ -3,11 +3,14 @@ import { useRole } from './useRole';
import { useUserConfig } from './useUserConfig'; import { useUserConfig } from './useUserConfig';
import axios from 'axios'; import axios from 'axios';
import useNotify from './useNotify'; import useNotify from './useNotify';
import { useTokenConfig } from './useTokenConfig';
const TOKEN_MULTIMEDIA = 'tokenMultimedia'; const TOKEN_MULTIMEDIA = 'tokenMultimedia';
const TOKEN = 'token'; const TOKEN = 'token';
export function useSession() { export function useSession() {
const { notify } = useNotify(); const { notify } = useNotify();
let isCheckingToken = false;
let intervalId = null;
function getToken() { function getToken() {
const localToken = localStorage.getItem(TOKEN); const localToken = localStorage.getItem(TOKEN);
@ -22,10 +25,24 @@ export function useSession() {
return localTokenMultimedia || sessionTokenMultimedia || ''; return localTokenMultimedia || sessionTokenMultimedia || '';
} }
function setToken(data) { function setSession(data) {
const storage = data.keepLogin ? localStorage : sessionStorage; let keepLogin = data.keepLogin;
const storage = keepLogin ? localStorage : sessionStorage;
storage.setItem(TOKEN, data.token); storage.setItem(TOKEN, data.token);
storage.setItem(TOKEN_MULTIMEDIA, data.tokenMultimedia); storage.setItem(TOKEN_MULTIMEDIA, data.tokenMultimedia);
storage.setItem('created', data.created);
storage.setItem('ttl', data.ttl);
sessionStorage.setItem('keepLogin', keepLogin);
}
function keepLogin() {
return sessionStorage.getItem('keepLogin');
}
function setToken({ token, tokenMultimedia }) {
const storage = keepLogin() ? localStorage : sessionStorage;
storage.setItem(TOKEN, token);
storage.setItem(TOKEN_MULTIMEDIA, tokenMultimedia);
} }
async function destroyToken(url, storage, key) { async function destroyToken(url, storage, key) {
if (storage.getItem(key)) { if (storage.getItem(key)) {
@ -45,11 +62,15 @@ export function useSession() {
tokenMultimedia: 'Accounts/logout', tokenMultimedia: 'Accounts/logout',
token: 'VnUsers/logout', token: 'VnUsers/logout',
}; };
const storage = keepLogin() ? localStorage : sessionStorage;
for (const [key, url] of Object.entries(tokens)) { for (const [key, url] of Object.entries(tokens)) {
await destroyToken(url, localStorage, key); await destroyToken(url, storage, key);
await destroyToken(url, sessionStorage, key);
} }
localStorage.clear();
sessionStorage.clear();
const { setUser } = useState(); const { setUser } = useState();
setUser({ setUser({
@ -59,22 +80,75 @@ export function useSession() {
lang: '', lang: '',
darkMode: null, darkMode: null,
}); });
stopRenewer();
} }
async function login(token, tokenMultimedia, keepLogin) { async function login(data) {
setToken({ token, tokenMultimedia, keepLogin }); setSession(data);
await useRole().fetch(); await useRole().fetch();
await useUserConfig().fetch(); await useUserConfig().fetch();
await useTokenConfig().fetch();
startInterval();
} }
function isLoggedIn() { function isLoggedIn() {
const localToken = localStorage.getItem(TOKEN); const localToken = localStorage.getItem(TOKEN);
const sessionToken = sessionStorage.getItem(TOKEN); const sessionToken = sessionStorage.getItem(TOKEN);
startInterval();
return !!(localToken || sessionToken); return !!(localToken || sessionToken);
} }
function startInterval() {
stopRenewer();
const renewPeriod = +sessionStorage.getItem('renewPeriod');
if (!renewPeriod) return;
intervalId = setInterval(() => checkValidity(), renewPeriod * 1000);
}
function stopRenewer() {
clearInterval(intervalId);
}
async function renewToken() {
const _token = getToken();
const token = await axios.post('VnUsers/renewToken', {
headers: { Authorization: _token },
});
const _tokenMultimedia = getTokenMultimedia();
const tokenMultimedia = await axios.post('VnUsers/renewToken', {
headers: { Authorization: _tokenMultimedia },
});
setToken({ token: token.data.id, tokenMultimedia: tokenMultimedia.data.id });
}
async function checkValidity() {
const { getTokenConfig } = useState();
const tokenConfig = getTokenConfig() ?? sessionStorage.getItem('tokenConfig');
const storage = keepLogin() ? localStorage : sessionStorage;
const created = +storage.getItem('created');
const ttl = +storage.getItem('ttl');
if (isCheckingToken || !created) return;
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 (isCheckingToken = false);
}
await renewToken();
isCheckingToken = false;
}
return { return {
getToken, getToken,
getTokenMultimedia, getTokenMultimedia,
@ -82,5 +156,8 @@ export function useSession() {
destroy, destroy,
login, login,
isLoggedIn, isLoggedIn,
checkValidity,
setSession,
renewToken,
}; };
} }

View File

@ -13,6 +13,7 @@ const user = ref({
}); });
const roles = ref([]); const roles = ref([]);
const tokenConfig = ref({});
const drawer = ref(true); const drawer = ref(true);
const headerMounted = ref(false); const headerMounted = ref(false);
@ -52,6 +53,15 @@ export function useState() {
function setRoles(data) { function setRoles(data) {
roles.value = data; roles.value = data;
} }
function getTokenConfig() {
return computed(() => {
return tokenConfig.value;
});
}
function setTokenConfig(data) {
tokenConfig.value = data;
}
function set(name, data) { function set(name, data) {
state.value[name] = ref(data); state.value[name] = ref(data);
@ -70,6 +80,8 @@ export function useState() {
setUser, setUser,
getRoles, getRoles,
setRoles, setRoles,
getTokenConfig,
setTokenConfig,
set, set,
get, get,
unset, unset,

View File

@ -0,0 +1,28 @@
import axios from 'axios';
import { useState } from './useState';
import useNotify from './useNotify';
export function useTokenConfig() {
const state = useState();
const { notify } = useNotify();
async function fetch() {
try {
const { data } = await axios.get('AccessTokenConfigs/findOne', {
filter: { fields: ['renewInterval', 'renewPeriod'] },
});
if (!data) return;
state.setTokenConfig(data);
sessionStorage.setItem('renewPeriod', data.renewPeriod);
return data;
} catch (error) {
notify('errors.tokenConfig', 'negative');
console.error('Error fetching token config:', error);
}
}
return {
fetch,
state,
};
}

View File

@ -106,6 +106,7 @@ errors:
statusBadGateway: It seems that the server has fall down statusBadGateway: It seems that the server has fall down
statusGatewayTimeout: Could not contact the server statusGatewayTimeout: Could not contact the server
userConfig: Error fetching user config userConfig: Error fetching user config
tokenConfig: Error fetching token config
writeRequest: The requested operation could not be completed writeRequest: The requested operation could not be completed
login: login:
title: Login title: Login

View File

@ -106,6 +106,7 @@ errors:
statusBadGateway: Parece ser que el servidor ha caído statusBadGateway: Parece ser que el servidor ha caído
statusGatewayTimeout: No se ha podido contactar con el servidor statusGatewayTimeout: No se ha podido contactar con el servidor
userConfig: Error al obtener configuración de usuario userConfig: Error al obtener configuración de usuario
tokenConfig: Error al obtener configuración de token
writeRequest: No se pudo completar la operación solicitada writeRequest: No se pudo completar la operación solicitada
login: login:
title: Inicio de sesión title: Inicio de sesión

View File

@ -38,7 +38,13 @@ async function onSubmit() {
if (!multimediaToken) return; if (!multimediaToken) return;
await session.login(data.token, multimediaToken.id, keepLogin.value); const login = {
...data,
created: Date.now(),
tokenMultimedia: multimediaToken.id,
keepLogin: keepLogin.value,
};
await session.login(login);
quasar.notify({ quasar.notify({
message: t('login.loginSuccess'), message: t('login.loginSuccess'),

View File

@ -12,6 +12,7 @@ import { useSession } from 'src/composables/useSession';
import { useRole } from 'src/composables/useRole'; import { useRole } from 'src/composables/useRole';
import { useUserConfig } from 'src/composables/useUserConfig'; import { useUserConfig } from 'src/composables/useUserConfig';
import { toLowerCamel } from 'src/filters'; import { toLowerCamel } from 'src/filters';
import { useTokenConfig } from 'src/composables/useTokenConfig';
const state = useState(); const state = useState();
const session = useSession(); const session = useSession();
@ -55,6 +56,7 @@ export default route(function (/* { store, ssrContext } */) {
if (stateRoles.length === 0) { if (stateRoles.length === 0) {
await useRole().fetch(); await useRole().fetch();
await useUserConfig().fetch(); await useUserConfig().fetch();
await useTokenConfig().fetch();
} }
const matches = to.matched; const matches = to.matched;
const hasRequiredRoles = matches.every((route) => { const hasRequiredRoles = matches.every((route) => {

View File

@ -1,5 +1,5 @@
import { vi, describe, expect, it } from 'vitest'; import { vi, describe, expect, it, beforeAll, beforeEach } from 'vitest';
import { axios } from 'app/test/vitest/helper'; import { axios, flushPromises } from 'app/test/vitest/helper';
import { useSession } from 'composables/useSession'; import { useSession } from 'composables/useSession';
import { useState } from 'composables/useState'; import { useState } from 'composables/useState';
@ -63,7 +63,9 @@ describe('session', () => {
}); });
}); });
describe('login', () => { describe(
'login',
() => {
const expectedUser = { const expectedUser = {
id: 999, id: 999,
name: `T'Challa`, name: `T'Challa`,
@ -96,7 +98,11 @@ describe('session', () => {
const expectedTokenMultimedia = 'mySessionTokenMultimedia'; const expectedTokenMultimedia = 'mySessionTokenMultimedia';
const keepLogin = false; const keepLogin = false;
await session.login(expectedToken,expectedTokenMultimedia, keepLogin); await session.login({
token: expectedToken,
tokenMultimedia: expectedTokenMultimedia,
keepLogin,
});
const roles = state.getRoles(); const roles = state.getRoles();
const localToken = localStorage.getItem('token'); const localToken = localStorage.getItem('token');
@ -119,7 +125,11 @@ describe('session', () => {
const expectedTokenMultimedia = 'myLocalTokenMultimedia'; const expectedTokenMultimedia = 'myLocalTokenMultimedia';
const keepLogin = true; const keepLogin = true;
await session.login(expectedToken, expectedTokenMultimedia, keepLogin); await session.login({
token: expectedToken,
tokenMultimedia: expectedTokenMultimedia,
keepLogin,
});
const roles = state.getRoles(); const roles = state.getRoles();
const localToken = localStorage.getItem('token'); const localToken = localStorage.getItem('token');
@ -131,5 +141,70 @@ describe('session', () => {
await session.destroy(); // this clears token and user for any other test await session.destroy(); // this clears token and user for any other test
}); });
},
{}
);
describe('RenewToken', () => {
const expectedToken = 'myToken';
const expectedTokenMultimedia = 'myTokenMultimedia';
const currentDate = new Date();
beforeAll(() => {
const tokenConfig = {
id: 1,
renewPeriod: 21600,
courtesyTime: 60,
renewInterval: 300,
};
state.setTokenConfig(tokenConfig);
sessionStorage.setItem('renewPeriod', 1);
});
it('NOT Should renewToken', async () => {
const data = {
token: expectedToken,
tokenMultimedia: expectedTokenMultimedia,
keepLogin: false,
ttl: 1,
created: Date.now(),
};
session.setSession(data);
expect(sessionStorage.getItem('keepLogin')).toBeFalsy();
expect(sessionStorage.getItem('created')).toBeDefined();
expect(sessionStorage.getItem('ttl')).toEqual(1);
await session.checkValidity();
expect(sessionStorage.getItem('token')).toEqual(expectedToken);
expect(sessionStorage.getItem('tokenMultimedia')).toEqual(
expectedTokenMultimedia
);
});
it('Should renewToken', async () => {
currentDate.setMinutes(currentDate.getMinutes() - 100);
const data = {
token: expectedToken,
tokenMultimedia: expectedTokenMultimedia,
keepLogin: false,
ttl: 1,
created: currentDate,
};
session.setSession(data);
vi.spyOn(axios, 'post')
.mockResolvedValueOnce({
data: { id: '' },
})
.mockResolvedValueOnce({
data: {
id: '',
},
});
expect(sessionStorage.getItem('keepLogin')).toBeFalsy();
expect(sessionStorage.getItem('created')).toBeDefined();
expect(sessionStorage.getItem('ttl')).toEqual(1);
await session.checkValidity();
expect(sessionStorage.getItem('token')).not.toEqual(expectedToken);
expect(sessionStorage.getItem('tokenMultimedia')).not.toEqual(
expectedTokenMultimedia
);
});
}); });
}); });

View File

@ -0,0 +1,31 @@
import { vi, describe, expect, it } from 'vitest';
import { axios, flushPromises } from 'app/test/vitest/helper';
import { useTokenConfig } from 'composables/useTokenConfig';
const tokenConfig = useTokenConfig();
describe('useTokenConfig', () => {
describe('fetch', () => {
it('should call setTokenConfig of the state with the expected data', async () => {
const data = {
id: 1,
renewPeriod: 21600,
courtesyTime: 60,
renewInterval: 300,
};
vi.spyOn(axios, 'get').mockResolvedValueOnce({
data,
});
vi.spyOn(tokenConfig.state, 'setTokenConfig');
tokenConfig.fetch();
await flushPromises();
expect(tokenConfig.state.setTokenConfig).toHaveBeenCalledWith(data);
const renewPeriod = sessionStorage.getItem('renewPeriod');
expect(renewPeriod).toEqual(data.renewPeriod);
});
});
});