/** * src/oauth/store.ts — OAuth authorization code store. * * Architecture: in-memory is always the source of truth for the MCP server * process. When Supabase env vars are present AND valid, codes are ALSO * persisted async to Supabase for durability and multi-instance lookup. * * This means: * - Unit tests (no env vars) → pure in-memory, zero network calls ✓ * - Production (Supabase set) → in-memory for hot-path, Supabase for * persistence/audit trail ✓ * * NOTE: The consent page (/api/oauth/approve) writes directly to Supabase * using the service role key without going through this store. The store * is used by the MCP server's own /oauth/* endpoints. * * Security: * - NEVER log the `code` value. Log only the row `id` (uuid). * - consumeCodeAsync marks codes consumed atomically in Supabase when * Supabase is reachable; falls through to in-memory otherwise. */ export interface IssuedCodeOptions { client_id: string; redirect_uri: string; code_challenge: string; code_challenge_method: "S256"; /** Optional user_id — set by consent page; undefined in unit tests */ user_id?: string; expiresInMs?: number; } export declare class OAuthStore { /** In-memory is ALWAYS the hot-path source of truth */ private codes; private refreshTokens; private cleanupTimer; constructor(); /** * Issue a new authorization code. * * Always writes to in-memory (fast, test-safe). * Also fire-and-forgets a Supabase persist when env vars are set. */ issueCode(options: IssuedCodeOptions): string; /** * Consume a code synchronously from in-memory (single-use). * This is the fast-path used by the token endpoint. */ consumeCode(code: string): (IssuedCodeOptions & { used: boolean; expires_at: Date; }) | null; /** * Async consume — checks in-memory first, then Supabase. * * Use this from token.ts so production deployments with Supabase * can survive process restarts (code issued before restart lives in DB). */ consumeCodeAsync(code: string): Promise<(IssuedCodeOptions & { used: boolean; expires_at: Date; }) | null>; /** * Refresh-token TTL — 90 days, sliding (reset on each rotation). * Matches GitHub's 6-month convention more closely than Supabase's 30d * default. Tradeoff: longer-lived refresh tokens = bigger blast radius * on leak; rotation + Supabase RLS mitigates this. An active user who * touches OE at least once per 90d gets an effectively permanent * connection. */ private static REFRESH_TTL_MS; storeRefreshToken(token: string, meta: object): void; /** * Synchronous in-memory consume. Kept for backward compat with any callers * that don't need cross-instance resilience. Token endpoint must use * consumeRefreshTokenAsync. */ consumeRefreshToken(token: string): object | null; /** * Async consume — checks in-memory first, then Supabase. This is what the * token endpoint uses so refresh requests survive machine restarts and * load-balancing across multiple Fly instances. */ consumeRefreshTokenAsync(token: string): Promise; generateRefreshToken(): string; reset(): void; private purgeExpired; destroy(): void; }