T13.1: idle timeout + absolute ceiling (D18)

Sessions now slide on activity (AUTH_IDLE_MINUTES, default 30) capped by
a hard ceiling from original sign-in (AUTH_SESSION_HOURS, meaning changed,
default 12 -> proposed 8). login_at carried across reissues so the ceiling
survives refreshes; pre-D18 tokens with no login_at fall back to iat.
Refresh is throttled (~IDLE_MINUTES/3) so the cookie isn't rewritten on
every request. Wired into auth_gate (server/app.py) - no DB hit, reads
only the already-validated claims.

Verified: 7 unit-level checks (fresh-token expiry, past-ceiling refusal,
throttling, mid-session extension, legacy-token fallback both live and
expired, idle cutoff itself) all pass, plus the full 27-check smoke
suite still passes end to end through the new middleware path.
This commit is contained in:
2026-09-23 11:40:59 -07:00
parent 358469531c
commit 6cde6e3f60
4 changed files with 120 additions and 7 deletions

View File

@@ -58,6 +58,15 @@ POSTGRES_PASSWORD=<strong-random-password>
# openssl rand -base64 48
AUTH_SECRET_KEY=<strong-random-secret>
# OPTIONAL — D18 (2026-09-23): a session slides on activity (AUTH_IDLE_MINUTES,
# default 30) capped by a hard ceiling from original sign-in regardless of
# activity (AUTH_SESSION_HOURS, default 8). Both are proposed defaults, not
# confirmed against this tenant's Okta SSO session policy — if Okta's own
# session outlives either one, re-auth here is likely a fast silent redirect
# rather than a real login screen. Full explanation in server/.env.example.
# AUTH_IDLE_MINUTES=30
# AUTH_SESSION_HOURS=8
# REQUIRED (in spirit — see the note below) — Okta OIDC is the only sign-in
# path (D15/D16). There is no local password anywhere in this app to fall back
# to, so without these nobody can sign in at all. Get them from the Okta app