agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
81 lines (80 loc) • 3.77 kB
TypeScript
/**
* verify/discovery — read an issuer's OpenID discovery document, and say which
* of three things happened.
*
* The classification is the point (rule 9: config errors refuse at boot,
* outages answer 503). A mistyped issuer path that answers 404 is REACHABLE BUT
* WRONG; calling it an outage would turn a typo into a permanent 503 that
* nobody is ever told to fix. So:
*
* • `outage` — a network error, a timeout, or a 5xx. The IdP may come back.
* • `misconfigured` — any other non-2xx (a redirect included: it is never
* followed), a body that is not a JSON object, a
* document with no `issuer` or no `jwks_uri`, an `issuer` that is not the
* configured one (compared exactly — OpenID Connect Discovery §4.3), or a
* key-set URL that is not `https`. The configuration names the wrong thing.
* • `ready` — a document that passed every check above.
*
* `access_token_issuer` (AD FS) is read from a `ready` document only. AD FS
* signs access tokens with `iss` = `http://<host>/adfs/services/trust` while
* its discovery `issuer` is `https://<host>/adfs`; a verifier pinned to the
* second refuses every AD FS access token. That value is COMPARED, never
* fetched, so the https rule does not apply to it.
*
* Nothing here throws: the three outcomes are values, and the caller decides
* what each means at boot and at request time.
*/
/** The checked facts a verifier needs out of the document. */
export interface DiscoveredIssuer {
/** The document's `issuer` — equal to the configured one by construction. */
readonly issuer: string;
/** Where the signing keys are. */
readonly jwksUri: string;
/** AD FS's second issuer for access tokens, when the document names one. */
readonly accessTokenIssuer?: string;
/** Where a browser is sent to sign in — browser sign-in needs it. */
readonly authorizationEndpoint?: string;
/** Where the code is exchanged — browser sign-in needs it. */
readonly tokenEndpoint?: string;
/** RP-Initiated Logout, when the IdP offers it. */
readonly endSessionEndpoint?: string;
/** RFC 9207: the IdP puts `iss` on every authorization response — then it is REQUIRED on the callback. */
readonly issParameterSupported?: boolean;
/** The PKCE methods the IdP names, when it names them. */
readonly codeChallengeMethods?: readonly string[];
}
export type DiscoveryOutcome = {
readonly kind: 'ready';
readonly issuer: DiscoveredIssuer;
} | {
readonly kind: 'outage';
readonly reason: string;
} | {
readonly kind: 'misconfigured';
readonly check: string;
};
/** The one fetch this module makes — injectable, so tests need no socket. */
export type DiscoveryFetch = (url: string, init: {
signal: AbortSignal;
headers: Record<string, string>;
redirect: 'manual';
}) => Promise<{
status: number;
text(): Promise<string>;
}>;
export interface ReadDiscoveryOptions {
readonly fetch: DiscoveryFetch;
readonly timeoutMs: number;
/** Plain `http` on a loopback host is accepted (development only). */
readonly allowLoopbackHttp: boolean;
}
/** `<issuer>/.well-known/openid-configuration`, a trailing `/` removed first. */
export declare function discoveryUrlFor(issuer: string): string;
/**
* Why `raw` is not a URL this library may fetch a credential-bearing answer
* from — or `undefined` when it is. `https`, or `http` on a loopback host when
* the caller allows it.
*/
export declare function fetchableUrlProblem(raw: string, allowLoopbackHttp: boolean): string | undefined;
/** Fetch and classify. Never throws. */
export declare function readDiscovery(configuredIssuer: string, options: ReadDiscoveryOptions): Promise<DiscoveryOutcome>;