#7190 - Renew tokenMultimedia #278
|
@ -3,11 +3,14 @@ import { useRole } from './useRole';
|
|||
import { useUserConfig } from './useUserConfig';
|
||||
import axios from 'axios';
|
||||
import useNotify from './useNotify';
|
||||
import { useTokenConfig } from './useTokenConfig';
|
||||
const TOKEN_MULTIMEDIA = 'tokenMultimedia';
|
||||
const TOKEN = 'token';
|
||||
|
||||
export function useSession() {
|
||||
const { notify } = useNotify();
|
||||
let isCheckingToken = false;
|
||||
let intervalId = null;
|
||||
|
||||
function getToken() {
|
||||
const localToken = localStorage.getItem(TOKEN);
|
||||
|
@ -22,10 +25,24 @@ export function useSession() {
|
|||
return localTokenMultimedia || sessionTokenMultimedia || '';
|
||||
}
|
||||
|
||||
function setToken(data) {
|
||||
const storage = data.keepLogin ? localStorage : sessionStorage;
|
||||
function setSession(data) {
|
||||
let keepLogin = data.keepLogin;
|
||||
const storage = keepLogin ? localStorage : sessionStorage;
|
||||
storage.setItem(TOKEN, data.token);
|
||||
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) {
|
||||
if (storage.getItem(key)) {
|
||||
|
@ -45,11 +62,15 @@ export function useSession() {
|
|||
tokenMultimedia: 'Accounts/logout',
|
||||
token: 'VnUsers/logout',
|
||||
};
|
||||
const storage = keepLogin() ? localStorage : sessionStorage;
|
||||
|
||||
for (const [key, url] of Object.entries(tokens)) {
|
||||
await destroyToken(url, localStorage, key);
|
||||
await destroyToken(url, sessionStorage, key);
|
||||
await destroyToken(url, storage, key);
|
||||
}
|
||||
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
|
||||
const { setUser } = useState();
|
||||
|
||||
setUser({
|
||||
|
@ -59,22 +80,75 @@ export function useSession() {
|
|||
lang: '',
|
||||
darkMode: null,
|
||||
});
|
||||
|
||||
stopRenewer();
|
||||
}
|
||||
|
||||
async function login(token, tokenMultimedia, keepLogin) {
|
||||
setToken({ token, tokenMultimedia, keepLogin });
|
||||
async function login(data) {
|
||||
setSession(data);
|
||||
|
||||
await useRole().fetch();
|
||||
await useUserConfig().fetch();
|
||||
await useTokenConfig().fetch();
|
||||
|
||||
startInterval();
|
||||
}
|
||||
|
||||
function isLoggedIn() {
|
||||
const localToken = localStorage.getItem(TOKEN);
|
||||
const sessionToken = sessionStorage.getItem(TOKEN);
|
||||
|
||||
startInterval();
|
||||
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 {
|
||||
getToken,
|
||||
getTokenMultimedia,
|
||||
|
@ -82,5 +156,8 @@ export function useSession() {
|
|||
destroy,
|
||||
login,
|
||||
isLoggedIn,
|
||||
checkValidity,
|
||||
setSession,
|
||||
renewToken,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ const user = ref({
|
|||
});
|
||||
|
||||
const roles = ref([]);
|
||||
const tokenConfig = ref({});
|
||||
const drawer = ref(true);
|
||||
const headerMounted = ref(false);
|
||||
|
||||
|
@ -52,6 +53,15 @@ export function useState() {
|
|||
function setRoles(data) {
|
||||
roles.value = data;
|
||||
}
|
||||
function getTokenConfig() {
|
||||
return computed(() => {
|
||||
return tokenConfig.value;
|
||||
});
|
||||
}
|
||||
|
||||
function setTokenConfig(data) {
|
||||
tokenConfig.value = data;
|
||||
}
|
||||
|
||||
function set(name, data) {
|
||||
state.value[name] = ref(data);
|
||||
|
@ -70,6 +80,8 @@ export function useState() {
|
|||
setUser,
|
||||
getRoles,
|
||||
setRoles,
|
||||
getTokenConfig,
|
||||
setTokenConfig,
|
||||
set,
|
||||
get,
|
||||
unset,
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
|
@ -106,6 +106,7 @@ errors:
|
|||
statusBadGateway: It seems that the server has fall down
|
||||
statusGatewayTimeout: Could not contact the server
|
||||
userConfig: Error fetching user config
|
||||
tokenConfig: Error fetching token config
|
||||
writeRequest: The requested operation could not be completed
|
||||
login:
|
||||
title: Login
|
||||
|
|
|
@ -106,6 +106,7 @@ errors:
|
|||
statusBadGateway: Parece ser que el servidor ha caído
|
||||
statusGatewayTimeout: No se ha podido contactar con el servidor
|
||||
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
|
||||
login:
|
||||
title: Inicio de sesión
|
||||
|
|
|
@ -38,7 +38,13 @@ async function onSubmit() {
|
|||
|
||||
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({
|
||||
message: t('login.loginSuccess'),
|
||||
|
|
|
@ -12,6 +12,7 @@ import { useSession } from 'src/composables/useSession';
|
|||
import { useRole } from 'src/composables/useRole';
|
||||
import { useUserConfig } from 'src/composables/useUserConfig';
|
||||
import { toLowerCamel } from 'src/filters';
|
||||
import { useTokenConfig } from 'src/composables/useTokenConfig';
|
||||
|
||||
const state = useState();
|
||||
const session = useSession();
|
||||
|
@ -55,6 +56,7 @@ export default route(function (/* { store, ssrContext } */) {
|
|||
if (stateRoles.length === 0) {
|
||||
await useRole().fetch();
|
||||
await useUserConfig().fetch();
|
||||
await useTokenConfig().fetch();
|
||||
}
|
||||
const matches = to.matched;
|
||||
const hasRequiredRoles = matches.every((route) => {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { vi, describe, expect, it } from 'vitest';
|
||||
import { axios } from 'app/test/vitest/helper';
|
||||
import { vi, describe, expect, it, beforeAll, beforeEach } from 'vitest';
|
||||
import { axios, flushPromises } from 'app/test/vitest/helper';
|
||||
import { useSession } from 'composables/useSession';
|
||||
import { useState } from 'composables/useState';
|
||||
|
||||
|
@ -63,73 +63,148 @@ describe('session', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('login', () => {
|
||||
const expectedUser = {
|
||||
id: 999,
|
||||
name: `T'Challa`,
|
||||
nickname: 'Black Panther',
|
||||
lang: 'en',
|
||||
userConfig: {
|
||||
darkMode: false,
|
||||
},
|
||||
};
|
||||
const rolesData = [
|
||||
{
|
||||
role: {
|
||||
name: 'salesPerson',
|
||||
describe(
|
||||
'login',
|
||||
() => {
|
||||
const expectedUser = {
|
||||
id: 999,
|
||||
name: `T'Challa`,
|
||||
nickname: 'Black Panther',
|
||||
lang: 'en',
|
||||
userConfig: {
|
||||
darkMode: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
role: {
|
||||
name: 'admin',
|
||||
};
|
||||
const rolesData = [
|
||||
{
|
||||
role: {
|
||||
name: 'salesPerson',
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
{
|
||||
role: {
|
||||
name: 'admin',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
it('should fetch the user roles and then set token in the sessionStorage', async () => {
|
||||
const expectedRoles = ['salesPerson', 'admin'];
|
||||
vi.spyOn(axios, 'get').mockResolvedValue({
|
||||
data: { roles: rolesData, user: expectedUser },
|
||||
it('should fetch the user roles and then set token in the sessionStorage', async () => {
|
||||
const expectedRoles = ['salesPerson', 'admin'];
|
||||
vi.spyOn(axios, 'get').mockResolvedValue({
|
||||
data: { roles: rolesData, user: expectedUser },
|
||||
});
|
||||
|
||||
const expectedToken = 'mySessionToken';
|
||||
const expectedTokenMultimedia = 'mySessionTokenMultimedia';
|
||||
const keepLogin = false;
|
||||
|
||||
await session.login({
|
||||
token: expectedToken,
|
||||
tokenMultimedia: expectedTokenMultimedia,
|
||||
keepLogin,
|
||||
});
|
||||
|
||||
const roles = state.getRoles();
|
||||
const localToken = localStorage.getItem('token');
|
||||
const sessionToken = sessionStorage.getItem('token');
|
||||
|
||||
expect(roles.value).toEqual(expectedRoles);
|
||||
expect(localToken).toBeNull();
|
||||
expect(sessionToken).toEqual(expectedToken);
|
||||
|
||||
await session.destroy(); // this clears token and user for any other test
|
||||
});
|
||||
|
||||
const expectedToken = 'mySessionToken';
|
||||
const expectedTokenMultimedia = 'mySessionTokenMultimedia';
|
||||
const keepLogin = false;
|
||||
it('should fetch the user roles and then set token in the localStorage', async () => {
|
||||
const expectedRoles = ['salesPerson', 'admin'];
|
||||
vi.spyOn(axios, 'get').mockResolvedValue({
|
||||
data: { roles: rolesData, user: expectedUser },
|
||||
});
|
||||
|
||||
await session.login(expectedToken,expectedTokenMultimedia, keepLogin);
|
||||
const expectedToken = 'myLocalToken';
|
||||
const expectedTokenMultimedia = 'myLocalTokenMultimedia';
|
||||
const keepLogin = true;
|
||||
|
||||
const roles = state.getRoles();
|
||||
const localToken = localStorage.getItem('token');
|
||||
const sessionToken = sessionStorage.getItem('token');
|
||||
await session.login({
|
||||
token: expectedToken,
|
||||
tokenMultimedia: expectedTokenMultimedia,
|
||||
keepLogin,
|
||||
});
|
||||
|
||||
expect(roles.value).toEqual(expectedRoles);
|
||||
expect(localToken).toBeNull();
|
||||
expect(sessionToken).toEqual(expectedToken);
|
||||
const roles = state.getRoles();
|
||||
const localToken = localStorage.getItem('token');
|
||||
const sessionToken = sessionStorage.getItem('token');
|
||||
|
||||
await session.destroy(); // this clears token and user for any other test
|
||||
expect(roles.value).toEqual(expectedRoles);
|
||||
expect(localToken).toEqual(expectedToken);
|
||||
expect(sessionToken).toBeNull();
|
||||
|
||||
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);
|
||||
|
||||
it('should fetch the user roles and then set token in the localStorage', async () => {
|
||||
const expectedRoles = ['salesPerson', 'admin'];
|
||||
vi.spyOn(axios, 'get').mockResolvedValue({
|
||||
data: { roles: rolesData, user: expectedUser },
|
||||
});
|
||||
|
||||
const expectedToken = 'myLocalToken';
|
||||
const expectedTokenMultimedia = 'myLocalTokenMultimedia';
|
||||
const keepLogin = true;
|
||||
|
||||
await session.login(expectedToken, expectedTokenMultimedia, keepLogin);
|
||||
|
||||
const roles = state.getRoles();
|
||||
const localToken = localStorage.getItem('token');
|
||||
const sessionToken = sessionStorage.getItem('token');
|
||||
|
||||
expect(roles.value).toEqual(expectedRoles);
|
||||
expect(localToken).toEqual(expectedToken);
|
||||
expect(sessionToken).toBeNull();
|
||||
|
||||
await session.destroy(); // this clears token and user for any other test
|
||||
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
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
¿?
Vaaale, puse ese comentario para que al probar la funcionalidad no tener que esperar mucho. El objetivo del comentario es que si la PR la coge alguien sin mucho contexto, puede invertir tiempo probando