переделал токен

This commit is contained in:
Zuev
2026-05-27 15:19:18 +03:00
parent d2ef44471b
commit 87cd4cbdc7
28 changed files with 1227 additions and 125 deletions

View File

@@ -1,38 +1,43 @@
const token = localStorage.getItem('token');
let refreshPromise = null;
const AUTH_KEYS = ['token', 'role', 'departmentId', 'userId'];
export function getToken() {
return token;
return localStorage.getItem('token');
}
export function isAuthenticatedAsAdmin() {
const role = localStorage.getItem('role');
return token && role === 'ADMIN';
return getToken() && role === 'ADMIN';
}
export function isAuthenticatedAsRole(expectedRole) {
const role = localStorage.getItem('role');
return token && role === expectedRole;
return getToken() && role === expectedRole;
}
export function isAuthenticatedAsAny(roles) {
const role = localStorage.getItem('role');
return token && roles.includes(role);
return getToken() && roles.includes(role);
}
function getHeaders(contentType = 'application/json') {
const headers = {
'Authorization': `Bearer ${token}`
};
const headers = {};
const token = getToken();
if (token) {
headers.Authorization = `Bearer ${token}`;
}
if (contentType) {
headers['Content-Type'] = contentType;
}
return headers;
}
export async function apiFetch(endpoint, method = 'GET', body = null) {
export async function apiFetch(endpoint, method = 'GET', body = null, retryOnUnauthorized = true) {
const options = {
method,
headers: getHeaders(body ? 'application/json' : null)
headers: getHeaders(body ? 'application/json' : null),
credentials: 'same-origin'
};
if (body) {
@@ -48,6 +53,15 @@ export async function apiFetch(endpoint, method = 'GET', body = null) {
data = null;
}
if (response.status === 401 && retryOnUnauthorized) {
const refreshed = await refreshAccessToken();
if (refreshed) {
return apiFetch(endpoint, method, body, false);
}
clearAuthState();
window.location.href = '/';
}
if (!response.ok) {
throw new Error(data?.message || `Ошибка HTTP: ${response.status}`);
}
@@ -55,6 +69,57 @@ export async function apiFetch(endpoint, method = 'GET', body = null) {
return data;
}
export async function refreshAccessToken() {
if (refreshPromise) {
return refreshPromise;
}
refreshPromise = (async () => {
const response = await fetch('/api/auth/refresh', {
method: 'POST',
credentials: 'same-origin'
});
const data = await response.json().catch(() => null);
if (!response.ok || !data?.token) {
return false;
}
storeAuthState(data);
return true;
})().finally(() => {
refreshPromise = null;
});
return refreshPromise;
}
export async function logout() {
try {
await fetch('/api/auth/logout', {
method: 'POST',
credentials: 'same-origin'
});
} catch (e) {
console.warn('Не удалось завершить серверную сессию:', e.message);
} finally {
clearAuthState();
}
}
export function storeAuthState(data) {
if (data.token) localStorage.setItem('token', data.token);
if (data.role) localStorage.setItem('role', data.role);
if (data.departmentId !== undefined && data.departmentId !== null) {
localStorage.setItem('departmentId', data.departmentId);
}
if (data.userId !== undefined && data.userId !== null) {
localStorage.setItem('userId', data.userId);
}
}
export function clearAuthState() {
AUTH_KEYS.forEach(key => localStorage.removeItem(key));
}
export const api = {
get: (url) => apiFetch(url, 'GET'),
post: (url, body) => apiFetch(url, 'POST', body),

View File

@@ -3,7 +3,7 @@ if (!['localhost', '127.0.0.1'].includes(window.location.hostname)) {
import('./otel.js').catch(e => console.warn('OTel init skipped:', e.message));
}
import { isAuthenticatedAsAny } from './api.js';
import { isAuthenticatedAsAny, logout } from './api.js';
import { applyRippleEffect, closeAllDropdownsOnOutsideClick } from './utils.js';
import { startDropdownAutoObserver, initAllCustomDropdowns } from './dropdown.js';
@@ -143,9 +143,8 @@ document.addEventListener('click', (e) => {
});
// Logout
btnLogout.addEventListener('click', () => {
localStorage.removeItem('token');
localStorage.removeItem('role');
btnLogout.addEventListener('click', async () => {
await logout();
window.location.href = '/';
});