UNPKG

agentfootprint

Version:

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

124 lines (123 loc) • 6.83 kB
/** * strategies/choose — `identityFromConfig`: config in, one strategy out, or a * boot refusal that names the key. * * Pattern: Strategy (GoF), chosen by CONFIG at install time — one per * deployment, behind the existing {@link IdentityVerifier} port. * Role: the one place a deployment's identity settings are judged. Every * door (`standingAgent`, an app's own routes) takes what this returns. * * ── The laws it enforces at boot (rule 9) ─────────────────────────────────── * • **Production names its strategy**, even `open`. Missing config must not * mean "no identity". `production` is an input the APP supplies; the * library never guesses it from `NODE_ENV`. * • **A typo is not `open`.** An unknown strategy, an unknown key, or a key a * later release reads is refused by name. * • **Somebody configured a sign-in and forgot to switch it on**: strategy * unset while `oidc-token` keys are set is refused, in every environment. * • **A half-configured verifier is not a smaller verifier**: every required * key is required; `IDENTITY_USER_ID_CLAIM` has no default. * • **Config errors refuse; outages answer 503.** Discovery runs at boot. A * 404, a body that is not JSON, a document without `jwks_uri` or naming * another issuer refuses to boot. An IdP that cannot be reached lets the * app start, says so in the banner, and answers 503 until it is reachable. * * Asynchronous — a refinement of the design's shape, on purpose: a discovery * config error must refuse the BOOT, and discovery is a network read. */ import type { CrossSiteOptions } from '../../../hosting/doorGuard.js'; import type { DoorIdentity } from '../../../hosting/identityVerification.js'; import type { SignInDoor } from '../../../hosting/signin/door.js'; import type { HostSignInOptions } from '../../../hosting/signin/types.js'; import type { SignInAuditSink } from '../../../hosting/signin/audit.js'; import { type DiscoveryFetch } from '../verify/discovery.js'; import type { JoseBackend } from '../verify/jwtCore.js'; import { oidcIdentity, type OidcIdentityOptions } from '../oidc.js'; import { type IdentityConfig } from './config.js'; import type { LdaptsBackend } from '../directory/ldapDirectory.js'; import type { OpenIdClientBackend } from '../oidcSignIn.js'; import { type IdentityStrategyName } from './vocabulary.js'; /** What the app tells the library about where it is running. */ export interface IdentityBootOptions { /** * Is this a production deployment? REQUIRED — the app decides (e.g. * `NODE_ENV === 'production' || APP_ENV === 'production'`), because only the * app knows every way it spells it. Production refuses an unset strategy and * plain-`http` issuers on `localhost`. */ readonly production: boolean; /** The fetch discovery uses. Default: the runtime's `fetch`. */ readonly fetch?: DiscoveryFetch; /** An already-imported `jose`, for a bundled app. */ readonly backend?: JoseBackend; /** * The door hardening's lists — the SAME `allowedHosts` / `allowedOrigins` * the host is built with. A sign-in strategy's door guard is built from * them (one source), and the public URL must pass them, or boot refuses. * Unset: only a loopback public URL (development) can start a sign-in door. */ readonly crossSite?: CrossSiteOptions; /** An already-imported `openid-client`, for a bundled app (browser sign-in). */ readonly openIdClient?: OpenIdClientBackend; /** An already-imported `ldapts`, for a bundled app (`directory-password`). */ readonly ldapts?: LdaptsBackend; /** * The sign-in door's typed audit trail (`signInDoor({ onAudit })`): one * record per sign-in outcome, for a sign-in strategy (`local-password`, * `directory-password`, `oidc-token` browser sign-in). Unset: the door * still writes one log line per outcome through `signInLog`. */ readonly onSignInAudit?: SignInAuditSink; /** Where the sign-in door's audit LINES go (`signInDoor({ log })`). Default `console.info`. */ readonly signInLog?: (line: string) => void; } /** What the page's sign-in gate should show — `GET /auth/config` answers it. */ export type IdentityMode = 'open' | 'token-only' | 'password' | 'redirect' | 'proxy'; /** The chosen strategy, ready to hand to every door. */ export interface IdentityChoice { /** Which strategy this deployment runs. */ readonly strategy: IdentityStrategyName; /** * What `standingAgent({ identity })` and every app door take. ABSENT for * `open`: nothing about a request changes. */ readonly identity?: DoorIdentity; /** What the page's sign-in gate shows: no sign-in, bearer tokens only, or a password form. */ readonly mode: IdentityMode; /** * The sign-in door (`/auth/*`), for a sign-in strategy. Mount it in front of * your routes: `nodeHost({ onUnhandled: (req, res) => door.handle(req, res) … })`. */ readonly signInDoor?: SignInDoor; /** * What the host takes for a sign-in strategy: `nodeHost({ signIn: choice.hostSignIn })`. * The transport then strips the sign-in cookie and checks every socket. */ readonly hostSignIn?: HostSignInOptions; /** * Lines to print at boot: the strategy, what it checks, what it does not, * and every warning. No secret, token, or claim value is ever in it. */ readonly banner: readonly string[]; } /** * Judge a deployment's identity config and build its strategy — or refuse to * boot with an {@link IdentityConfigError} naming the key. * * @example * const choice = await identityFromConfig(identityConfigFromEnv(process.env), { * production: process.env.NODE_ENV === 'production', * }); * for (const line of choice.banner) console.log(line); * const host = await standingAgent({ agent, sessions, host, identity: choice.identity }); */ export declare function identityFromConfig(config: IdentityConfig, boot: IdentityBootOptions): Promise<IdentityChoice>; /** The verifier's own construction refusals are boot refusals too. */ export declare function buildVerifier(options: OidcIdentityOptions): ReturnType<typeof oidcIdentity>; export declare function oidcOptions(config: IdentityConfig, boot: IdentityBootOptions, production: boolean, shape?: { readonly strategy: 'oidc-token' | 'proxy-token'; readonly literalIssuer: boolean; }): OidcIdentityOptions; /** A banner line with any control character escaped — belt and braces for values from a document. */ export declare function bannerSafe(line: string): string; export declare function checkUrl(url: string, field: string, production: boolean): void;