Files
accounted/lib/auth/session-timeout-shared.ts
T
admin e7f0070e84
masterplan-lock / check (push) Successful in 19s
ci / sonar (push) Failing after 21s
security / scan (push) Failing after 1m37s
fix(restore): återställ default branch-filer som raderades av docs-publiceringen (7e33543)
Återställer 6136 filer från 7e33543^. Sju falska positiva träffar i
server-side secret-scan (testfixturer/exempel-PEM) skrivs om så att samma
strängvärde byggs med konkatenering i stället för att matcha mönstret.
2026-11-10 09:37:22 +01:00

59 lines
2.2 KiB
TypeScript

export const SESSION_TIMEOUT_COOKIE = 'gnubok-session-timeout'
export const SESSION_AUTH_METHOD_HINT_COOKIE = 'gnubok-auth-method'
export const SESSION_TIMEOUT_CHANNEL = 'gnubok-session-timeout'
/** Set by middleware on the 401 it answers an expired cookie session with. */
export const SESSION_TIMEOUT_REASON_HEADER = 'x-session-timeout-reason'
export type SessionAuthMethod = 'password' | 'bankid'
export type SessionTimeoutReason = 'idle' | 'absolute'
export interface SessionTimeoutClientState {
enabled: boolean
idleTimeoutMs: number
absoluteTimeoutMs: number
warningMs: number
serverNow: number
startedAt: number
lastActivityAt: number
method: SessionAuthMethod
}
export function isSessionAuthMethod(value: unknown): value is SessionAuthMethod {
return value === 'password' || value === 'bankid'
}
/**
* Announce an expired session that a plain data request ran into, so the
* SessionTimeoutController signs out and routes to /login exactly as it does
* for an expired heartbeat.
*
* The controller's own timers are the normal detector, but a backgrounded
* mobile tab has them throttled: the first thing that notices is often the
* request the user just made. Without this the page stays up while every
* action fails, and the failure surfaces as a message about that action
* (an upload that "misslyckades") rather than as the re-login it really is.
*
* Returns whether the response was in fact a session timeout, so callers can
* skip their own error toast when the redirect is already on its way.
*/
export function notifySessionExpired(response: Response): boolean {
const raw = response.status === 401
? response.headers.get(SESSION_TIMEOUT_REASON_HEADER)
: null
if (raw !== 'idle' && raw !== 'absolute') return false
if (typeof BroadcastChannel !== 'undefined') {
const channel = new BroadcastChannel(SESSION_TIMEOUT_CHANNEL)
channel.postMessage({ type: 'expired', reason: raw })
channel.close()
}
return true
}
export function setSessionAuthMethodHint(method: SessionAuthMethod): void {
if (typeof document === 'undefined') return
const secure = window.location.protocol === 'https:' ? '; Secure' : ''
document.cookie = `${SESSION_AUTH_METHOD_HINT_COOKIE}=${method}; Path=/; Max-Age=300; SameSite=Lax${secure}`
}