/** * The session manager - `createSessions()` returns get/commit/destroy/regenerate bound to a * signing secret + cookie config. Two modes: * • **store mode** (a {@link SessionStore} is given): a random session id rides a signed cookie; the * data lives in the store. Big/sensitive data, server-side revocation. * • **cookie mode** (no store): the data itself is signed into the cookie (stateless, edge-friendly, * ≤4 KB). No server state. * The cookie value is **always HMAC-signed** (tamper-evident; verified constant-time) and the session * cookie is always `HttpOnly`. Reads **fail closed** - a tampered/missing/expired cookie yields a fresh * anonymous session, never an error. */ import { type CookieOptions } from "@nifrajs/core/cookies"; import { type SessionStore } from "./store.js"; /** The cookie + response surface the manager needs - a structural subset of nifra's `Context`, so any * `c` satisfies it and it's testable with a stub. */ export interface SessionContext { readonly cookies: Readonly>; /** Optional raw request, used to reject ambiguous duplicate session cookies. */ readonly request?: Request; readonly set: { cookie(name: string, value: string, options?: CookieOptions): void; deleteCookie(name: string, options?: { readonly path?: string; readonly domain?: string; }): void; }; } /** A typed session handle. Every key is optional - a fresh session is empty. */ export interface Session = Record> { get(key: K): Data[K] | undefined; set(key: K, value: Data[K]): void; unset(key: keyof Data): void; has(key: keyof Data): boolean; /** Wipe all data (the session id is kept). To end the session entirely, call `manager.destroy`. */ clear(): void; /** A snapshot of the current data. */ readonly data: Readonly>; readonly isEmpty: boolean; } /** Cookie attributes a session may tune. `httpOnly` is **not** offered - a session cookie is always * HttpOnly; `maxAge`/`expires` are derived from the session's lifetime. */ export type SessionCookieOptions = Pick; export interface SessionOptions { /** Signing secret (≥ 32 bytes), or a rotation list: the **first** secret signs new cookies and * any listed secret verifies, so rotating without invalidating live sessions is: prepend the new * secret, drop the old one after `maxAge` has elapsed. A bare secret swap (no list) invalidates * all existing sessions. */ readonly secret: string | readonly string[]; /** Strongly consistent backend for **store mode**; omit for **cookie mode** (stateless). * Declared eventually consistent stores (including KVSessionStore) are refused. */ readonly store?: SessionStore; /** Cookie name. Default `nifra_session`. */ readonly cookieName?: string; /** Session lifetime in **seconds**. Default 7 days. */ readonly maxAge?: number; /** Slide the expiry forward on every commit (default `true`); `false` = fixed absolute expiry. */ readonly rolling?: boolean; /** Extra cookie attributes (the session cookie is always HttpOnly; `Secure`/`SameSite=Lax`/`Path=/` * default via `c.set.cookie`). Pass `{ secure: false }` for local http dev. */ readonly cookie?: SessionCookieOptions; /** Clock (ms) - injected for testability; production passes `() => Date.now()` (the default). */ readonly now?: () => number; /** * Also accept a session cookie signed by `@nifrajs/auth` before 5.0, whose signature does not cover * the cookie name, so upgrading from 4.x signs no one out. Off by default: while it is on, any * value the app signs with this secret through `@nifrajs/core`'s `signValue` (a signed preference * cookie, say) and that parses as a session reads as one. Turn it on for one `maxAge` after the * upgrade, then remove it; each such session is re-signed in the current form on its next commit. */ readonly acceptLegacyCookies?: boolean; } export interface SessionManager = Record> { /** Load the session from the request cookie (verified + un-expired), or a fresh anonymous one. */ get(c: SessionContext): Promise>; /** Read-only load from a raw `Request` - for `@nifrajs/web` loaders (which can read the request but not * write cookies). Identical verify/expiry to {@link get}; commit/destroy in a route or action. */ read(request: Request): Promise>; /** Persist the session: write the store (store mode) + the signed cookie. */ commit(c: SessionContext, session: Session): Promise; /** End the session: drop the store record (store mode) + clear the cookie. In store mode the id is * also marked revoked, so a request that loaded the session earlier cannot write it back. The * `session` passed in is emptied and detached: committing it later in the same request starts a * new session holding only what was set since. A session object not passed in stays bound to the * ended id, and committing it writes nothing. */ destroy(c: SessionContext, session?: Session): Promise; /** Rotate the session id on the next commit (call on privilege change - login - to defend against * session fixation). Data is preserved; the old store record is dropped. */ regenerate(session: Session): void; } export declare function createSessions = Record>(options: SessionOptions): SessionManager; //# sourceMappingURL=session.d.ts.map