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 { 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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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'),
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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