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
TypeScript
/**
* 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;