mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 21:10:59 +00:00
Token verification: jwtVerify now carries a closed asymmetric algorithm allowlist (RS256/PS256/ES256/EdDSA — an HMAC alg would let anyone with the client secret forge identities) and a 60 s clock tolerance for the usual self-host drift between IdP and app box. The JWKS fetch — jose's own, outside safeFetch by design — is disciplined instead by pinning the jwks_uri host to the configured issuer's host byte-exact and passing the same 15 s timeout safeFetch defaults to. requirePublicHost stays deliberately unused on this surface: LAN/Tailscale-only IdPs are a legitimate topology and the issuer is operator config, not user input. Discovery: the issuer comparison now tolerates exactly one trailing slash on either side (a recurring IdP/operator mismatch); token verification still matches the provider-sent issuer byte-exact. State cookie: the cookie is set under Path=/api/auth/oidc but was deleted without a path — RFC 6265 keys cookies by (name, domain, path), so the deletion never matched and the single-use blob survived its whole TTL. Every delete now repeats the exact path, via a shared constant. Also: the IdP error code recorded in the wide event is whitelisted against the closed OAuth error-code set (unknown values collapse to "other"); the status route drops an unreachable try/catch and a duplicated annotate; the passkey conditional-UI autofill stays unarmed under OIDC_ONLY (passkey login is a server-enforced dead end there); env-manifest gains the three optional OIDC_* tuning vars; the env example notes why the issuer should be HTTPS.
32 lines
1.6 KiB
TypeScript
32 lines
1.6 KiB
TypeScript
/**
|
|
* Shared between `/api/auth/oidc/login` (writer) and `/api/auth/oidc/callback`
|
|
* (reader) — the encrypted, single-use blob carrying `{state, nonce,
|
|
* codeVerifier, next}` across the browser round-trip to the IdP and back.
|
|
*/
|
|
export const OIDC_STATE_COOKIE = "oidc_auth_state";
|
|
/**
|
|
* RFC 6265 keys a cookie by (name, domain, path) — a delete must repeat the
|
|
* exact path the set used or it silently targets a different (non-existent)
|
|
* cookie and the stale state blob survives. Every set AND delete goes
|
|
* through this constant.
|
|
*/
|
|
export const OIDC_STATE_COOKIE_PATH = "/api/auth/oidc";
|
|
export const OIDC_STATE_TTL_MS = 10 * 60 * 1000;
|
|
|
|
/**
|
|
* MFA handoff between `/api/auth/oidc/callback` (writer) and the login page
|
|
* (reader). When the OIDC account has a native second factor, the callback
|
|
* mints the same single-use MFA ticket password login uses — but it ends on
|
|
* a browser redirect, not a JSON response, so the ticket travels in this
|
|
* short-lived, login-page-scoped cookie instead of an envelope `meta`.
|
|
* Deliberately NOT httpOnly: the login page's script must read the ticket —
|
|
* exactly the exposure the password flow already has, where the ticket
|
|
* arrives in a JSON body read by the same script. The ticket alone carries
|
|
* no session authority (single-use, hashed at rest, ~5-minute TTL,
|
|
* attempt-capped — see `src/lib/auth/mfa/challenge.ts`).
|
|
*/
|
|
export const OIDC_MFA_COOKIE = "oidc_mfa_handoff";
|
|
export const OIDC_MFA_COOKIE_PATH = "/auth/login";
|
|
/** Mirrors MFA_CHALLENGE_TTL_MS — the cookie dies with the ticket. */
|
|
export const OIDC_MFA_TTL_MS = 5 * 60 * 1000;
|