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.
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
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);
|
||||
Reference in New Issue
Block a user