diff --git a/apps/backoffice/src/app/features/user-management/UserManagementPage.tsx b/apps/backoffice/src/app/features/user-management/UserManagementPage.tsx index a79dc9ed0..6a96bba84 100644 --- a/apps/backoffice/src/app/features/user-management/UserManagementPage.tsx +++ b/apps/backoffice/src/app/features/user-management/UserManagementPage.tsx @@ -1,3 +1,4 @@ +import Cookies from 'js-cookie'; import { useCallback, useEffect, useRef } from 'react'; import { createRoot, type Root } from 'react-dom/client'; import { useNavigate } from 'react-router-dom'; @@ -138,12 +139,8 @@ export default function UserManagementPage() { style.textContent = UM_OVERRIDES; document.head.appendChild(style); - 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 token = Cookies.get('ema-backoffice-auth-token') ?? ''; + const refreshToken = Cookies.get('ema-backoffice-refresh-token'); const session: UserManagementSessionOptions = { initialSession: token diff --git a/libs/api/src/lib/session/index.ts b/libs/api/src/lib/session/index.ts index 7a3c1326d..7a38f30e1 100644 --- a/libs/api/src/lib/session/index.ts +++ b/libs/api/src/lib/session/index.ts @@ -1,3 +1,5 @@ +import Cookies from 'js-cookie'; + export const SESSION_HEADER_KEYS = { tenantId: 'x-tenant-id', organizationUnitId: 'x-organization-unit-id', @@ -11,17 +13,10 @@ 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); + const cookie = Cookies.get(key); if (cookie) return cookie; const stored = localStorage.getItem(key); if (stored) return stored; diff --git a/libs/auth/src/lib/components/ProtectedRoute.tsx b/libs/auth/src/lib/components/ProtectedRoute.tsx index 9eb4d0e8f..9c55d85ee 100644 --- a/libs/auth/src/lib/components/ProtectedRoute.tsx +++ b/libs/auth/src/lib/components/ProtectedRoute.tsx @@ -1,3 +1,4 @@ +import Cookies from 'js-cookie'; import { Navigate, Outlet, useLocation } from 'react-router-dom'; import type { ReactNode } from 'react'; import { authStorage } from '../utils/auth-storage'; @@ -7,14 +8,9 @@ interface ProtectedRouteProps { loginPath?: string; } -function getTokenFromCookie(): string | undefined { - const match = document.cookie.match(/(?:^|;\s*)auth-token=([^;]*)/); - return match ? decodeURIComponent(match[1]) : undefined; -} - export function ProtectedRoute({ children, loginPath = '/login' }: ProtectedRouteProps) { const location = useLocation(); - const token = authStorage.getToken() ?? getTokenFromCookie(); + const token = authStorage.getToken() ?? Cookies.get('auth-token'); if (!token) { return ; diff --git a/libs/auth/src/lib/store/auth.slice.ts b/libs/auth/src/lib/store/auth.slice.ts index 0a68218b8..9b64d9475 100644 --- a/libs/auth/src/lib/store/auth.slice.ts +++ b/libs/auth/src/lib/store/auth.slice.ts @@ -1,6 +1,6 @@ import { createSlice, type PayloadAction } from '@reduxjs/toolkit'; import type { AuthState, AuthUser, CurrentProfile, LoginPayload } from '../types/auth.types'; -import { authStorage } from '../utils/auth-storage'; +import { authStorage, clearAllCookies } from '../utils/auth-storage'; const initialState: AuthState = { user: null, @@ -37,6 +37,7 @@ const authSlice = createSlice({ state.isAuthenticated = false; state.currentProfile = null; authStorage.clear(); + clearAllCookies() }, hydrateAuth(state) { const token = authStorage.getToken(); diff --git a/libs/auth/src/lib/utils/auth-storage.ts b/libs/auth/src/lib/utils/auth-storage.ts index 9b9f05af7..9354d5474 100644 --- a/libs/auth/src/lib/utils/auth-storage.ts +++ b/libs/auth/src/lib/utils/auth-storage.ts @@ -1,4 +1,6 @@ -const MAX_AGE = 60 * 60 * 24 * 7; // ponytail: cookie lifetime knob; JWT expiry + refresh govern real auth +import Cookies from 'js-cookie'; + +const COOKIE_EXPIRES_DAYS = 7; // ponytail: cookie lifetime knob; JWT expiry + refresh govern real auth interface Backend { get(k: string): string | null; @@ -13,27 +15,25 @@ const localBackend: Backend = { }; const cookieBackend: Backend = { - get: (k) => { - const m = document.cookie.match(new RegExp('(?:^|;\\s*)' + k + '=([^;]*)')); - return m ? decodeURIComponent(m[1]) : null; - }, + get: (k) => Cookies.get(k) ?? 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`; - }, + set: (k, v) => + Cookies.set(k, v, { path: '/', secure: true, sameSite: 'strict', expires: COOKIE_EXPIRES_DAYS }), + remove: (k) => Cookies.remove(k, { path: '/' }), }; -import Cookies from "js-cookie"; +const AUTH_KEY_NAMES = ['auth-token', 'refresh-token', 'auth-user', 'profile-id', 'current-profile']; -let _prefix = "ema-auth"; +let _prefix = 'ema-auth'; let backend: Backend = localBackend; export function configureAuthStorage(prefix: string, useCookies = false) { _prefix = prefix; backend = useCookies ? cookieBackend : localBackend; + if (useCookies) { + // one-time cleanup: drop pre-migration localStorage leftovers so tokens live in cookies only + AUTH_KEY_NAMES.forEach((k) => localStorage.removeItem(`${prefix}-${k}`)); + } } function key(k: string) { @@ -68,16 +68,14 @@ export const authStorage = { [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"; + Cookies.remove('auth-token', { path: '/' }); }, }; export const clearAllCookies = () => { const cookies = Cookies.get(); - Object.keys(cookies).forEach((name) => { Cookies.remove(name); - Cookies.remove(name, { path: "/" }); + Cookies.remove(name, { path: '/' }); }); };