/** * GitHub OAuth Device Flow primitives for the dashboard sign-in path. * * Why a parallel module to `@onenomad/przm-cortex-github-auth`: * That package serves the CLI's `cortex github-login` flow — it wraps * start + poll into a single high-level call with its own retry loop. * The dashboard needs lower-level handles: a `/start` HTTP route * issues the device code, the browser polls `/poll` on its own * schedule so the user can watch a spinner with progress, and the * server returns a one-shot status on each poll rather than blocking. * Sharing the wrapper would force the HTTP request to block until * GitHub approves — not what the browser wants. * * These primitives are thin transport adapters; they don't store state. * The route layer (`dashboard-auth-github.ts`) holds the per-flow stash * in memory and decides allowlist + session-binding policy. * * Reference: * https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#device-flow */ /** * Placeholder Client ID. Production builds replace this string at * publish time with the real OneNomad-owned OAuth app id. Until the * lead swaps it in, `start()` will return a 502 from GitHub — which is * fine for tests that mock `fetch`. Search for this exact constant when * doing the pre-publish swap. * * The runtime resolution order is: `PRZM_CORTEX_GITHUB_OAUTH_CLIENT_ID` * from the workspace env (self-hosted operators bring their own OAuth * app) → `PRZM_CORTEX_DEFAULT_GITHUB_CLIENT_ID` from the process env * (build-time injection) → this placeholder. See `resolveClientId`. */ export declare const DEFAULT_ONENOMAD_CLIENT_ID: string; export interface DeviceFlowStart { userCode: string; verificationUri: string; deviceCode: string; expiresIn: number; interval: number; } export interface DeviceFlowDeps { fetchImpl?: typeof fetch; } /** * Open a device-flow grant. Returns the short user code (which the * browser displays so the user can type it into github.com/login/device) * plus the device code (held server-side and used during polling). */ export declare function startDeviceFlow(clientId: string, deps?: DeviceFlowDeps): Promise; export type DeviceFlowPollStatus = "pending" | "slow_down" | "authorized" | "expired" | "denied" | "error"; export interface DeviceFlowPollResult { status: DeviceFlowPollStatus; /** Present when `status === "authorized"`. */ accessToken?: string; /** Space-separated scope string GitHub returned with the token. */ scopes?: string[]; /** Surfaced verbatim from GitHub for `error` status. */ errorDescription?: string; } /** * One-shot poll of the GitHub token endpoint. The caller decides * cadence — we hand back whichever terminal/transient state GitHub * reports without blocking. `slow_down` is mapped to its own status so * the route layer can lengthen its interval on the next call. */ export declare function pollDeviceFlow(clientId: string, deviceCode: string, deps?: DeviceFlowDeps): Promise; export interface GitHubUser { login: string; id: number; email: string | null; name: string | null; avatarUrl: string | null; } /** * Resolve the authenticated GitHub user. Falls back to /user/emails for * the primary verified address when /user returns null (which it does * when the user has marked their email private). Email is best-effort — * the allowlist check below only requires `login`. */ export declare function fetchGitHubUser(accessToken: string, deps?: DeviceFlowDeps): Promise; /** * Pick the OAuth Client ID for the active flow. * * 1. workspace env `PRZM_CORTEX_GITHUB_OAUTH_CLIENT_ID` — self-hosters * who want to point Cortex at their own GitHub OAuth app override * here. This is the only path that lets `/callback` web-flow * eventually work (once a SECRET is also supplied). * 2. `DEFAULT_ONENOMAD_CLIENT_ID` — the OneNomad-published Cortex * OAuth app id. Baked into the binary at publish time. * * Returns the resolved id and a tag describing which path was taken so * the route layer can log/troubleshoot. */ export declare function resolveClientId(workspaceEnv: ReadonlyMap): { clientId: string; source: "workspace" | "default"; }; /** * Parse the comma-separated allowlist env var. Whitespace and case are * normalized — `Matt`, ` matt `, `MATT` all match. Empty / missing → * empty set (which means "nobody is allowlisted" and every login is * rejected with `not_allowlisted`; intentional fail-closed default). */ export declare function parseAllowlist(raw: string | undefined): ReadonlySet; /** Case-insensitive membership check against a parsed allowlist. */ export declare function isAllowlisted(allowlist: ReadonlySet, login: string): boolean; //# sourceMappingURL=github-oauth.d.ts.map