Files
jobtrackingapp/job-tracker-ui/src/sessionBoundary.ts
T
cesnimda 0954876295 fix(security): enforce audited account, network and resource boundaries
Implement B01-B06 with regression coverage and retained validation evidence. Preserve the production ingress preflight; F04 runtime verification remains open.
2026-09-09 18:21:47 +02:00

54 lines
2.2 KiB
TypeScript

import { getCsrfToken, setAuthUserKey } from "./auth";
export const SESSION_INVALIDATED_EVENT = "session-invalidated";
const SESSION_SIGNAL_KEY = "authSessionContext";
let bound: { id: string; context: string; csrf: string | null } | null = null;
let invalidated = false;
const requests = new AbortController();
export function isSessionInvalidated() { return invalidated; }
export function sessionRequestSignal() { return requests.signal; }
export function getSessionContext() { return invalidated ? null : bound?.context ?? null; }
export function invalidateSession() {
if (invalidated) return;
invalidated = true;
requests.abort();
window.dispatchEvent(new Event(SESSION_INVALIDATED_EVENT));
}
// A page never adopts a second session. Reloading disposes old forms, promises, caches,
// dialogs and retry callbacks together; none can obtain the next account's write context.
export function bindSession(id: string, context: string) {
if (invalidated) return false;
if (!id || !context || (bound && (bound.id !== id || bound.context !== context))) {
invalidateSession();
return false;
}
bound ??= { id, context, csrf: getCsrfToken() };
setAuthUserKey(id, false);
try { window.localStorage.setItem(SESSION_SIGNAL_KEY, context); } catch { /* Cookie/focus checks still apply. */ }
return true;
}
export function checkSessionCookie() {
if (bound && bound.csrf !== getCsrfToken()) invalidateSession();
}
window.addEventListener("storage", event => {
if (!bound) return;
if (event.key === null ||
(event.key === SESSION_SIGNAL_KEY && event.newValue !== bound.context) ||
(event.key === "authUserKey" && event.newValue !== bound.id)) invalidateSession();
});
window.addEventListener("auth-user-changed", checkSessionCookie);
window.addEventListener("focus", checkSessionCookie);
window.addEventListener("pageshow", event => {
// A BFCache snapshot can contain private state and outstanding callbacks from an old session.
if (event.persisted && bound) invalidateSession();
else checkSessionCookie();
});
window.addEventListener("visibilitychange", checkSessionCookie);
// Fallback when cookies change through another client without a storage event.
window.setInterval(checkSessionCookie, 1000);