Merge branch 'estif-branch-1' of https://github.com/Tria-plc/emaui into estif-branch-1

This commit is contained in:
Estifo77
2026-07-22 12:38:40 +03:00
6 changed files with 71 additions and 51 deletions

View File

@@ -138,8 +138,12 @@ export default function UserManagementPage() {
style.textContent = UM_OVERRIDES;
document.head.appendChild(style);
const token = localStorage.getItem('ema-backoffice-auth-token') ?? '';
const refreshToken = localStorage.getItem('ema-backoffice-refresh-token') ?? undefined;
const readCookie = (name: string) => {
const m = document.cookie.match(new RegExp('(?:^|;\\s*)' + name + '=([^;]*)'));
return m ? decodeURIComponent(m[1]) : undefined;
};
const token = readCookie('ema-backoffice-auth-token') ?? '';
const refreshToken = readCookie('ema-backoffice-refresh-token');
const session: UserManagementSessionOptions = {
initialSession: token

View File

@@ -11,7 +11,7 @@ import {
import type { AuthUser, CurrentProfile } from '@ema-platform/auth';
import { preferencesReducer } from './preferences.slice';
configureAuthStorage('ema-backoffice');
configureAuthStorage('ema-backoffice', true);
const preloadedAuth = (() => {
const token = authStorage.getToken();

View File

@@ -11,13 +11,22 @@ const TOKEN_STORAGE_KEYS = [
'auth-token',
] as const;
function getCookie(name: string): string | undefined {
const match = document.cookie.match(
new RegExp('(?:^|;\\s*)' + name + '=([^;]*)'),
);
return match ? decodeURIComponent(match[1]) : undefined;
}
export function resolveTokenFromStorage(): string | undefined {
// cookie first (backoffice stores tokens there), then localStorage (portal / legacy)
for (const key of TOKEN_STORAGE_KEYS) {
const cookie = getCookie(key);
if (cookie) return cookie;
const stored = localStorage.getItem(key);
if (stored) return stored;
}
const match = document.cookie.match(/(?:^|;\s*)auth-token=([^;]*)/);
return match ? decodeURIComponent(match[1]) : undefined;
return undefined;
}
export function resolveSessionContext(state?: { auth?: { token?: string } }): {

View File

@@ -1,9 +1,39 @@
const MAX_AGE = 60 * 60 * 24 * 7; // ponytail: cookie lifetime knob; JWT expiry + refresh govern real auth
interface Backend {
get(k: string): string | null;
set(k: string, v: string): void;
remove(k: string): void;
}
const localBackend: Backend = {
get: (k) => localStorage.getItem(k),
set: (k, v) => localStorage.setItem(k, v),
remove: (k) => localStorage.removeItem(k),
};
const cookieBackend: Backend = {
get: (k) => {
const m = document.cookie.match(new RegExp('(?:^|;\\s*)' + k + '=([^;]*)'));
return m ? decodeURIComponent(m[1]) : null;
},
// ponytail: 4KB/cookie cap — auth-user/current-profile JSON must stay under it
set: (k, v) => {
document.cookie = `${k}=${encodeURIComponent(v)}; path=/; Secure; SameSite=Strict; Max-Age=${MAX_AGE}`;
},
remove: (k) => {
document.cookie = `${k}=; path=/; Secure; SameSite=Strict; Max-Age=0`;
},
};
import Cookies from "js-cookie";
let _prefix = "ema-auth";
let backend: Backend = localBackend;
export function configureAuthStorage(prefix: string) {
export function configureAuthStorage(prefix: string, useCookies = false) {
_prefix = prefix;
backend = useCookies ? cookieBackend : localBackend;
}
function key(k: string) {
@@ -11,44 +41,33 @@ function key(k: string) {
}
export const authStorage = {
getToken: () => localStorage.getItem(key("auth-token")) ?? undefined,
setToken: (token: string) => localStorage.setItem(key("auth-token"), token),
getRefreshToken: () =>
localStorage.getItem(key("refresh-token")) ?? undefined,
setRefreshToken: (t: string) => localStorage.setItem(key("refresh-token"), t),
getToken: () => backend.get(key('auth-token')) ?? undefined,
setToken: (token: string) => backend.set(key('auth-token'), token),
getRefreshToken: () => backend.get(key('refresh-token')) ?? undefined,
setRefreshToken: (t: string) => backend.set(key('refresh-token'), t),
getUser: <T = unknown>(): T | null => {
try {
return JSON.parse(
localStorage.getItem(key("auth-user")) ?? "null",
) as T | null;
return JSON.parse(backend.get(key('auth-user')) ?? 'null') as T | null;
} catch {
return null;
}
},
setUser: <T>(u: T) =>
localStorage.setItem(key("auth-user"), JSON.stringify(u)),
getProfileId: () => localStorage.getItem(key("profile-id")) ?? undefined,
setProfileId: (id: string) => localStorage.setItem(key("profile-id"), id),
setUser: <T>(u: T) => backend.set(key('auth-user'), JSON.stringify(u)),
getProfileId: () => backend.get(key('profile-id')) ?? undefined,
setProfileId: (id: string) => backend.set(key('profile-id'), id),
getProfile: <T = unknown>(): T | null => {
try {
return JSON.parse(
localStorage.getItem(key("current-profile")) ?? "null",
) as T | null;
return JSON.parse(backend.get(key('current-profile')) ?? 'null') as T | null;
} catch {
return null;
}
},
setProfile: <T>(p: T) =>
localStorage.setItem(key("current-profile"), JSON.stringify(p)),
removeProfile: () => localStorage.removeItem(key("current-profile")),
setProfile: <T>(p: T) => backend.set(key('current-profile'), JSON.stringify(p)),
removeProfile: () => backend.remove(key('current-profile')),
clear: () => {
[
key("auth-token"),
key("refresh-token"),
key("auth-user"),
key("profile-id"),
key("current-profile"),
].forEach((k) => localStorage.removeItem(k));
[key('auth-token'), key('refresh-token'), key('auth-user'), key('profile-id'), key('current-profile')].forEach((k) =>
backend.remove(k),
);
document.cookie =
"auth-token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC; SameSite=Lax";
},

26
package-lock.json generated
View File

@@ -45,7 +45,7 @@
"@nx/vitest": "^22.5.4",
"@types/js-cookie": "^3.0.6",
"@types/node": "^22.0.0",
"@types/react": "^19.0.0",
"@types/react": "^19.2.17",
"@types/react-dom": "^19.0.0",
"@typescript-eslint/eslint-plugin": "^8.60.0",
"@typescript-eslint/parser": "^8.60.0",
@@ -3869,18 +3869,6 @@
}
}
},
"node_modules/@mui/x-date-pickers/node_modules/@types/react": {
"version": "18.3.31",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz",
"integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.2.2"
}
},
"node_modules/@mui/x-date-pickers/node_modules/react-is": {
"version": "19.2.7",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.7.tgz",
@@ -9199,9 +9187,9 @@
"optional": true
},
"node_modules/@types/react": {
"version": "19.2.15",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.15.tgz",
"integrity": "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==",
"version": "19.2.17",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz",
"integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==",
"license": "MIT",
"dependencies": {
"csstype": "^3.2.2"
@@ -11943,7 +11931,7 @@
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
"dev": true,
"devOptional": true,
"license": "MIT",
"dependencies": {
"iconv-lite": "^0.6.2"
@@ -13734,7 +13722,7 @@
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"dev": true,
"devOptional": true,
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
@@ -17901,7 +17889,7 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true,
"devOptional": true,
"license": "MIT"
},
"node_modules/sax": {

View File

@@ -50,7 +50,7 @@
"@nx/vitest": "^22.5.4",
"@types/js-cookie": "^3.0.6",
"@types/node": "^22.0.0",
"@types/react": "^19.0.0",
"@types/react": "^19.2.17",
"@types/react-dom": "^19.0.0",
"@typescript-eslint/eslint-plugin": "^8.60.0",
"@typescript-eslint/parser": "^8.60.0",