forked from verdnatura/salix-front
Reviewed-on: verdnatura/salix-front#278 Reviewed-by: Alex Moreno <alexm@verdnatura.es>
This commit is contained in:
commit
6482f5680d
|
@ -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,7 +63,9 @@ describe('session', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('login', () => {
|
||||
describe(
|
||||
'login',
|
||||
() => {
|
||||
const expectedUser = {
|
||||
id: 999,
|
||||
name: `T'Challa`,
|
||||
|
@ -96,7 +98,11 @@ describe('session', () => {
|
|||
const expectedTokenMultimedia = 'mySessionTokenMultimedia';
|
||||
const keepLogin = false;
|
||||
|
||||
await session.login(expectedToken,expectedTokenMultimedia, keepLogin);
|
||||
await session.login({
|
||||
token: expectedToken,
|
||||
tokenMultimedia: expectedTokenMultimedia,
|
||||
keepLogin,
|
||||
});
|
||||
|
||||
const roles = state.getRoles();
|
||||
const localToken = localStorage.getItem('token');
|
||||
|
@ -119,7 +125,11 @@ describe('session', () => {
|
|||
const expectedTokenMultimedia = 'myLocalTokenMultimedia';
|
||||
const keepLogin = true;
|
||||
|
||||
await session.login(expectedToken, expectedTokenMultimedia, keepLogin);
|
||||
await session.login({
|
||||
token: expectedToken,
|
||||
tokenMultimedia: expectedTokenMultimedia,
|
||||
keepLogin,
|
||||
});
|
||||
|
||||
const roles = state.getRoles();
|
||||
const localToken = localStorage.getItem('token');
|
||||
|
@ -131,5 +141,70 @@ describe('session', () => {
|
|||
|
||||
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
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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