UNPKG

agentfootprint

Version:

The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.

76 lines (75 loc) • 3.91 kB
/** * hosting/signin/redirectRoutes — the sign-in door's redirect half: * `GET /auth/login` and `GET /auth/callback` (design §5.2). PENDING * INDEPENDENT REVIEW before a company install. * * `GET /auth/login` * Check `returnTo` (§5.5). Make `state`, `nonce` and (unless PKCE is off) a * PKCE verifier. SEAL them, with `returnTo` and the time, into this * attempt's transaction cookie. Nothing is written on the server. Redirect * to the identity provider. * * `GET /auth/callback`, in this order: * 1. Open the transaction cookie named by `state` — sealed by this server, * for this attempt, less than 10 minutes old — and compare `state`. * 2. Clear the transaction cookie, WHATEVER the outcome: `state` is single use. * 3. The strategy exchanges the code (client authentication + the PKCE * verifier), checks the ID token (`iss`, `aud`, `nonce`, `exp`) and runs * its OWN `verify` on the access token — the person test included. * 4. End any sign-in the browser already carries; create the new one. * 5. 303 to `returnTo`, with `Referrer-Policy: no-referrer` so the code in * this URL does not leak onward. * A failure is a 303 to `/?signin_error=<code>`: a fixed reason code, never * the identity provider's text. * * Bounded per browser (review idI57 S-2): a login keeps at most the newest * {@link KEPT_PENDING} OTHER pending transaction cookies and expires the rest, * and a sealed transaction is at most {@link MAX_SEALED_BYTES} — so no page * can stack enough cookies on this origin to make every request a 431. * * A failure that is the deployment's, not the browser's (the IdP or the store * unreachable, the library missing) is logged by CLASS, once per change. * * Two `GET` routes change state, deliberately: the login sets a cookie and the * callback creates a sign-in. Both are safe — the callback needs the sealed * cookie issued to THIS browser, a matching `state`, and a single-use code * bound to the PKCE verifier and nonce. */ /// <reference types="node" /> import type { IncomingMessage, ServerResponse } from 'node:http'; import type { SignInOutcome, SignInReason } from './audit.js'; import { type SealKey } from './seal.js'; import { type RedirectSignIn, type SignInAccepted } from './types.js'; /** What the door hands the redirect half. */ export interface RedirectContext { readonly strategy: RedirectSignIn; readonly publicUrl: URL; readonly prefix: string; readonly cookie: { txName(attempt: string): string; issueTx(attempt: string, sealed: string): string; expireTx(attempt: string): string; }; readonly sealKey: SealKey; readonly now: () => number; /** Where an operator line goes (a failure class, once per change). */ readonly warn: (message: string) => void; endPresent(req: IncomingMessage): Promise<boolean>; startSignIn(accepted: SignInAccepted, strategy: string): Promise<{ setCookie: string; body: Record<string, string>; }>; /** File the callback's outcome in the door's audit trail (the door adds strategy, address, time). */ audit(req: IncomingMessage, outcome: SignInOutcome, reason: SignInReason, userId?: string): void; } export interface RedirectRoutes { login(req: IncomingMessage, res: ServerResponse): Promise<void>; callback(req: IncomingMessage, res: ServerResponse): Promise<void>; /** Where the page goes after sign-out: the IdP's end-session URL, or `/`. */ logoutNext(): Promise<string>; } /** Pending transactions a login keeps besides its own — so one browser holds at most three. */ export declare const KEPT_PENDING = 2; /** The largest sealed transaction, in bytes; a larger one is re-sealed with `returnTo` = `/`. */ export declare const MAX_SEALED_BYTES = 1200; export declare function redirectRoutesFor(ctx: RedirectContext): RedirectRoutes;