/** * Auth resolver skeletons — the shared *shape* of the credential-resolution * logic duplicated across the fleet (resy / opentable / ofw / zola / * signupgenius / creditkarma / canvas / infinitecampus auth.ts). * * This module owns the **skeleton** only. Per-site parameters (which env var, * which cookies to declare, how to parse the session blob, which CSRF input to * scrape) are *injected*; nothing here branches on site identity. Site-specific * OAuth choreographies (e.g. Skylight's `/auth/session` → `/oauth/authorize` → * `/oauth/token` dance) stay per-MCP — they are not a shared shape. * * Four pieces: * - {@link createAuthResolver} — the three-path resolver * (env credential → fetchproxy one-shot read → actionable error). * - {@link resolveAuthPattern} — the four-path variant * (token → OAuth → session-scrape → fetchproxy), each path an injected * resolver tried in priority order. * - {@link sessionLoginFlow} — CSRF-scrape + cookie POST + success-marker, * the shared CSRF+cookie login primitive (canvas / IC / ofw / signupgenius). * - {@link createOAuth2Refresher} — an OAuth2 `refresh_token`-grant refresher * with optional retry and in-flight race-safety. * * Security posture: env reads go through the hardened {@link readEnvVar} * (placeholder / `'null'` / `'undefined'` suppression); thrown errors never * echo the offending secret value and run upstream bodies through * {@link truncateErrorMessage} (redaction + truncation) before surfacing. * * fetchproxy bridge-down hints are preserved: when the injected bootstrap * rejects with a `FetchproxyBridgeDownError` (duck-typed — this core module * stays zero-runtime-dep and never imports `@fetchproxy/server` or the * `/fetchproxy` subpath), {@link createAuthResolver} surfaces the error's * actionable `.hint` verbatim ("make sure a signed-in tab is open… reload * the extension…") instead of the generic truncated wrap. This absorbs the * `classifyBridgeError(e) === 'bridge_down'` branch the fleet copies of this * skeleton hand-rolled, and unblocks the zola / infinitecampus / ofw / * creditkarma migrations onto this resolver in a later wave. * ({@link resolveAuthPattern} never had the gap — it propagates a configured * path's errors unwrapped, so an injected fetchproxy path's hint survives.) */ import { type EnvSource } from '../config/index.js'; /** A `@fetchproxy/bootstrap` session blob: declared cookies / storage, by key. */ export interface FetchproxySession { cookies: Record; localStorage?: Record; sessionStorage?: Record; } /** * The injected `@fetchproxy/bootstrap`-shaped function. Kept as a parameter (not * a hard import) so the heavy bridge dep stays out of this module and tests can * mock the module boundary, exactly as every fleet `auth.ts` does today. */ export type BootstrapFn = (opts: unknown) => Promise; /** Result of {@link createAuthResolver}'s resolver — opaque credential + provenance. */ export interface ResolvedCredential { /** The resolved credential (token / cookie header / refresh JWT — opaque). */ credential: string; /** Which path produced it. Diagnostics / cache-keying only — do not branch on it. */ source: 'env' | 'fetchproxy'; } /** Options for {@link createAuthResolver}. */ export interface AuthResolverOptions { /** Env var holding the credential when the user supplies it directly (path 1). */ envVar: string; /** * Env var that, when truthy, disables the fetchproxy fallback (path 2). When * omitted, the fallback is always attempted. Mirrors `*_DISABLE_FETCHPROXY`. */ disableEnvVar?: string; /** Injected `@fetchproxy/bootstrap` function (mocked at the boundary in tests). */ bootstrap: BootstrapFn; /** The opts object passed verbatim to {@link bootstrap} (domains, declare, …). */ bootstrapOptions: unknown; /** * Lift the credential out of the fetchproxy session blob. Returns the * credential string, or `undefined`/`''` when the signed-in tab didn't carry * it (→ surfaced as a "sign in" error). */ parseTokens: (session: FetchproxySession) => string | undefined; /** Human-readable service name for the not-signed-in error (e.g. "Zola"). */ serviceName?: string; /** Host to point the user at when the browser session is missing (e.g. "zola.com"). */ signInHost?: string; /** Env source. Defaults to {@link process.env}. */ env?: EnvSource; } /** * Build the canonical **three-path** auth resolver: * * 1. **Env credential** — `envVar` set (after hardened {@link readEnvVar} * sanitization) → returned directly, no network. * 2. **fetchproxy one-shot read** — unless `disableEnvVar` is truthy, call the * injected `bootstrap` to snapshot the user's signed-in browser session, * then `parseTokens` to extract the credential. fetchproxy is invoked once; * it is never in the hot path. * 3. **Actionable error** — nothing configured: an error naming the env var * and the sign-in fallback so the user can pick a fix. * * The returned `source` is for diagnostics; callers must treat the credential * as opaque and not branch on it. */ export declare function createAuthResolver(opts: AuthResolverOptions): () => Promise; /** Result of a {@link resolveAuthPattern} path resolver — opaque credential + provenance. */ export interface PatternResult { /** The resolved credential (bearer / cookie header — opaque to the caller). */ credential: string; /** Which path produced it. Diagnostics only — callers should not branch on it. */ source: string; } /** A single path resolver. Returning a value claims the path; throwing aborts. */ export type PathResolver = () => Promise; /** * The four ordered paths of the "Pattern A template" (canvas / IC). A path is * **configured** iff its resolver is provided; the *first* configured path, in * this fixed priority order, runs. This is the only ordering — it never branches * on which site is calling. */ export interface AuthPattern { /** Path 1: a stateless personal-access-token credential. */ token?: PathResolver; /** Path 2: an OAuth `refresh_token` grant. */ oauth?: PathResolver; /** Path 3: a username/password session-scrape (CSRF + cookie login). */ sessionScrape?: PathResolver; /** Path 4: a fetchproxy one-shot browser-session read. */ fetchproxy?: PathResolver; } /** * Resolve auth via the four-path priority **token → OAuth → session-scrape → * fetchproxy**. Runs the first *provided* resolver in that order (a missing * resolver = an unconfigured path). A resolver that throws propagates — a * partial-config error (the user's mistake) must surface, not silently fall * through. Errors propagate *unwrapped*, so an injected fetchproxy path's * bridge-down `.hint` survives intact. Throws an actionable error when no * path is configured at all. */ export declare function resolveAuthPattern(pattern: AuthPattern): Promise; /** Options for {@link sessionLoginFlow}. */ export interface SessionLoginOptions { /** URL of the login page to GET (carries the CSRF input + sets a session cookie). */ loginUrl: string; /** URL to POST the credentials to. */ postUrl: string; /** Regex with one capture group that extracts the CSRF token from the page HTML. */ csrfRegex: RegExp; /** Form field name the CSRF token is submitted under. Defaults to `'csrfToken'`. */ csrfField?: string; /** * Cookie name that signals a successful login *and* is returned as `token`. * Its presence after the POST is the success marker. */ tokenField: string; /** Form field name the email/username is submitted under. Defaults to `'email'`. */ emailField?: string; /** Form field name the password is submitted under. Defaults to `'password'`. */ passwordField?: string; /** The user's email / username. */ email: string; /** The user's password. */ password: string; /** Extra static form fields to include in the POST body (per-site form params). */ extraFields?: Record; /** Header sent on both requests (e.g. a desktop `User-Agent`). */ userAgent?: string; /** Injectable fetch (defaults to global `fetch`) — for tests. */ fetchImpl?: typeof fetch; } /** Result of {@link sessionLoginFlow}. */ export interface SessionLoginResult { /** Value of the `tokenField` cookie set on a successful login. */ token: string; /** Full `Cookie` header (deduped jar) for subsequent authenticated requests. */ cookies: string; } /** * The shared CSRF + cookie login primitive (canvas / IC / ofw / signupgenius): * * 1. GET `loginUrl` — capture the session cookie(s) and scrape the CSRF token * out of the page with `csrfRegex`. * 2. POST `postUrl` (`application/x-www-form-urlencoded`) with the scraped CSRF * token, credentials, and any `extraFields`, carrying the GET's cookies. * 3. Merge `Set-Cookie`s from both responses (deduped, deletions dropped) and * require the `tokenField` cookie as the success marker — its value is the * returned `token`; its absence means the credentials were rejected. * * Per-site form parameters and the CSRF regex are injected; the flow itself is * site-agnostic. */ export declare function sessionLoginFlow(opts: SessionLoginOptions): Promise; /** Options for {@link createOAuth2Refresher}. */ export interface OAuth2RefresherOptions { /** Token endpoint to POST the grant to. */ endpoint: string; /** The refresh token to exchange. */ refreshToken: string; /** OAuth2 grant type. Defaults to `'refresh_token'`. */ grantType?: string; /** Extra form params (e.g. `client_id`, `client_secret`, `scope`). */ params?: Record; /** * Retry policy for a failed exchange. `count` is *additional* attempts after * the first; `delayMs` is the fixed wait between attempts. Omit to never retry. */ retry?: { count: number; delayMs: number; }; /** Injectable fetch (defaults to global `fetch`) — for tests. */ fetchImpl?: typeof fetch; } /** Result of an {@link createOAuth2Refresher} exchange. */ export interface OAuth2RefreshResult { /** The new access token. */ accessToken: string; /** A rotated refresh token, when the server returned one. */ refreshToken?: string; /** `expires_in` (seconds), when the server returned one. */ expiresIn?: number; /** Absolute expiry (`now + expires_in`), when `expires_in` was present. */ expiresAt?: Date; } /** * Build a race-safe OAuth2 `refresh_token`-grant refresher. The returned * function POSTs the form-encoded grant to `endpoint` and parses the standard * `{ access_token, refresh_token?, expires_in? }` body. * * Race-safety: concurrent calls share a single in-flight exchange (the * canonical token-refresh-race guard — `skylight`/`canvas`/`creditkarma`/`zola` * all hand-roll this). The in-flight promise is cleared once it settles, so a * later refresh starts fresh and a *rejected* exchange does not poison the next * caller. * * Errors run through {@link truncateErrorMessage} (redaction + truncation) * before surfacing, so an upstream error body can't leak a bearer token or * blow up a tool result. */ export declare function createOAuth2Refresher(opts: OAuth2RefresherOptions): () => Promise; export * from './cached-token.js'; export * from './es256.js'; //# sourceMappingURL=index.d.ts.map