5c346820ea
closes the ttl-policy + revocation pieces left from #110. absolute-timeout middleware: every request that survives express-session runs through a new middleware that checks req.session.absoluteExpiresAt. past the 12 h hard ceiling, it destroys the redis-side session, clears the portal_session cookie, drops the entry from the per-user index, and lets the request keep flowing anonymously. route-level guards (/me today, future @requireauth) turn that into a 401 where auth is actually needed — public routes stay accessible. not the "every route returns 401" reading of adr-0010 §"ttl policy" (validated with the project lead): "returns 401" is interpreted as "the user eventually sees a 401 when they touch something that needs auth", not as a hard refusal on the current in-flight request. user_sessions:{userId} secondary index (adr-0010 §"revocation"): a new UserSessionIndexService maintains a redis set of active session ids per user. wired on /auth/callback (sadd at sign-in, after session.save() so the session row exists before the index points at it) and on /auth/logout + the absolute-timeout middleware (srem on destroy). best-effort: a failed sadd/srem logs a pino warning and the auth flow continues — the index is a convenience for admin operations, not a security invariant. orphans (entries whose session:<id> redis key has expired on idle ttl) are tolerated per the adr; future consumer code filters them out. no in-product consumer ships in this pr — the admin "logout everywhere" endpoint lands with the admin module (+ @requireadmin / @requiremfa guards). today the index is write-only on the bff side. session payload extension: createdAt and absoluteExpiresAt are now set on the session at the same moment as req.session.user, in /auth/callback. the session.types.ts declaration merging exposes them as optional SessionData fields so every consumer sees a typed payload. wiring: - SessionModule provides UserSessionIndexService and a SESSION_ABSOLUTE_TIMEOUT_MIDDLEWARE token whose factory builds the middleware via createAbsoluteTimeoutMiddleware(index, logger). same pattern as the existing SESSION_MIDDLEWARE token. - AuthModule now imports SessionModule so AuthController can inject UserSessionIndexService. - main.ts mounts the absolute-timeout middleware immediately after the session middleware. per-user identifier is the entra `oid` claim (stable per-user inside the tenant, matches req.session.user.oid). future multi-tenant scenarios may want `${tid}:${oid}` — easy refactor when external id activation lands (adr-0008). not in scope here. out of scope, landing in follow-ups: - admin "logout everywhere" endpoint consuming UserSessionIndexService.list(userId) - audit-pipeline first-class events for session.absolute_timeout + user_session_index.* (adr-0013) - token blob persistence (id_token / access_token / refresh_token) in the encrypted session — adr-0014 dependency 123/123 tests pass (was 110); +13 specs across the two new files and the auth controller spec.