# `session` _(no submodules)_ ## Purpose The rolling-JWT session resolver — the keystone of the rolled auth adapter. Builds a `SessionResolver` (per `@suaveplan/api-auth`) that parses-and-verifies a JWT extracted from either the `Authorization: Bearer` header or a configurable cookie, runs the JWT through the `TokenRotator` to find the verifying JWK by `kid`, projects the matching user row onto a `Principal`, and returns `null` on every soft failure so the host's resolver chain can move on to the next adapter. The same module exposes `issueToken(userId)` so the future account-lifecycle provider (deferred to `api-auth-rolled-provider`) can share a single signing path with the resolver. Keeping issue and verify co-located means the JWT shape and the rotator wiring stay in lock-step. ## Features - RS256 signing via `@suaveplan/jws` `compactSign` with the rotator's active JWK; verification via `compactVerify` against the rotator's retired-or-active JWK matched by `kid`. - Bearer + cookie extraction in one pass — bearer wins when both are present so service clients can override a stale cookie. - Strict JWT-claim validation: `iss`/`aud` exact match, `exp` future, `iat` not before `users.createdAt`. - Soft failures (missing token, bad signature, expired, unknown kid) collapse to `null`; hard failures (storage adapter throws) propagate as `AuthRolledError`. - `buildSetCookie` returns a deterministic `Set-Cookie` value with `HttpOnly`, `Path=/`, `Max-Age`, `SameSite`, `Secure` — used by the future provider half but published here so the cookie shape lives next to the cookie parser. ## Usage: Basic ```ts import { createRolledSessionResolver, TokenRotator } from "@suaveplan/api-auth-rolled"; const rotator = new TokenRotator({ keys, rotationPeriodMs, clock }); const resolver = createRolledSessionResolver({ options, rotator, clock, cookieName: "suaveplan.session", sessionTtlSec: 3600, }); const principal = await resolver.resolve(req); ``` ## Usage: Advanced ```ts import { createRolledSessionResolver } from "@suaveplan/api-auth-rolled"; const resolver = createRolledSessionResolver({ /* ... */ }); // Provider half (future) signs new tokens with the same key set as the // resolver — sharing one path means the rotator's view of the active // kid is the only source of truth. const issued = await resolver.issueToken("u_1"); const setCookie = buildSetCookie({ name: "suaveplan.session", value: issued.token, maxAgeSec: issued.expiresAt - issued.issuedAt, sameSite: "lax", secure: true, }); ``` ## API Reference - `createRolledSessionResolver({ options, rotator, clock, cookieName, sessionTtlSec }) → SessionResolver & { issueToken, verifyToken }` - `buildSetCookie({ name, value, maxAgeSec, sameSite, secure }) → string` - `tokenSizeOf(token: string): number` — byte length, used by the conformance harness to assert tokens stay under the 8 KiB cookie limit. ## Implementation Notes The resolver intentionally returns `null` on every parse/verify failure rather than throwing — `SessionResolver` chain semantics treat thrown errors as operator-actionable infrastructure failures, so soft failures must collapse to `null` to let the chain advance to the next adapter (typically `api-auth-anonymous`). The two cases where the resolver does throw are `AuthRolledError(PRINCIPAL_PROJECTION_FAILED)` raised by the projection step (storage-driver bug) and whatever error the operator's `store.findUserById` raises (DB connection lost). The session-resolver chain handles both by surfacing 500 to the client and routing to the host's audit log. The header parse is done manually rather than via `compactDecode` so a malformed header (e.g. missing `kid`) bypasses the rotator lookup entirely. The rotator can verify with retired-but-non-revoked keys, so rotating the active key never invalidates a session mid-lifetime — operators can rotate without coordinating with active users.