mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-26 13:02:50 +00:00
feat: update authentication storage to use cookies and improve token retrieval
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 } }): {
|
||||
|
||||
@@ -1,7 +1,37 @@
|
||||
let _prefix = 'ema-auth';
|
||||
const MAX_AGE = 60 * 60 * 24 * 7; // ponytail: cookie lifetime knob; JWT expiry + refresh govern real auth
|
||||
|
||||
export function configureAuthStorage(prefix: string) {
|
||||
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`;
|
||||
},
|
||||
};
|
||||
|
||||
let _prefix = 'ema-auth';
|
||||
let backend: Backend = localBackend;
|
||||
|
||||
export function configureAuthStorage(prefix: string, useCookies = false) {
|
||||
_prefix = prefix;
|
||||
backend = useCookies ? cookieBackend : localBackend;
|
||||
}
|
||||
|
||||
function key(k: string) {
|
||||
@@ -9,32 +39,32 @@ 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),
|
||||
backend.remove(k),
|
||||
);
|
||||
document.cookie =
|
||||
'auth-token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC; SameSite=Lax';
|
||||
|
||||
26
package-lock.json
generated
26
package-lock.json
generated
@@ -43,7 +43,7 @@
|
||||
"@nx/vite": "^22.5.4",
|
||||
"@nx/vitest": "^22.5.4",
|
||||
"@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",
|
||||
@@ -3867,18 +3867,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",
|
||||
@@ -9190,9 +9178,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"
|
||||
@@ -11934,7 +11922,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"
|
||||
@@ -13725,7 +13713,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"
|
||||
@@ -17892,7 +17880,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": {
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
"@nx/vite": "^22.5.4",
|
||||
"@nx/vitest": "^22.5.4",
|
||||
"@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",
|
||||
|
||||
Reference in New Issue
Block a user