// @ts-nocheck // // Token Rotation Map (codex-multi-auth pattern) — extracted from // open-sse/services/tokenRefresh.ts. See ../shared.ts for provenance notes. // // When a rotating-token provider (Codex, Kimi, GitLab Duo, etc.) refreshes, // the old refresh_token is consumed and a new one is issued. Any subsequent // caller arriving with the OLD token would, without protection, hit upstream // and trigger "refresh_token_reused" — which Auth0 treats as a security event // and invalidates the entire token family. // // This in-memory map caches RECENT rotations so a stale caller can be redirected // to the new tokens WITHOUT touching upstream. The DB staleness check inside // the per-connection mutex covers the same scenario when connectionId is known, // but not all callers pass connectionId (e.g., legacy code paths, retries that // snapshot credentials before the rotation lands in DB). // // Ported from ndycode/codex-multi-auth (lib/refresh-queue.ts:218-248), the only // publicly known tool that reliably sustains multiple Codex OAuth accounts. // // Key format: `provider:sha256(oldRefreshToken)` // Value: { result: tokens, expiresAt: ms_since_epoch } import { pbkdf2Sync } from "node:crypto"; const CACHE_SECRET = "omniroute-token-cache"; /** * Build the dedup/rotation cache key for a (provider, refreshToken) pair. * Hashed so a raw refresh_token never sits in a Map key in plaintext. */ export function getRefreshCacheKey(provider, refreshToken) { const tokenHash = pbkdf2Sync(refreshToken, CACHE_SECRET, 1000, 32, "sha256").toString("hex"); return `${provider}:${tokenHash}`; } type RotationEntry = { result: { accessToken: string; refreshToken: string; expiresIn?: number; expiresAt?: string }; expiresAt: number; }; const tokenRotationMap = new Map(); const ROTATION_MAP_TTL_MS = 60 * 1000; // 60 seconds — long enough to catch in-flight stale callers function cleanupRotationMap(now: number = Date.now()): void { if (tokenRotationMap.size === 0) return; for (const [key, entry] of tokenRotationMap.entries()) { if (entry.expiresAt <= now) tokenRotationMap.delete(key); } } export function lookupRotation(provider: string, refreshToken: string): RotationEntry | undefined { cleanupRotationMap(); const key = getRefreshCacheKey(provider, refreshToken); const entry = tokenRotationMap.get(key); if (!entry) return undefined; if (entry.expiresAt <= Date.now()) { tokenRotationMap.delete(key); return undefined; } return entry; } export function recordRotation( provider: string, oldRefreshToken: string, result: { accessToken: string; refreshToken: string; expiresIn?: number; expiresAt?: string } ): void { if (!oldRefreshToken || !result.refreshToken || oldRefreshToken === result.refreshToken) { return; } const key = getRefreshCacheKey(provider, oldRefreshToken); tokenRotationMap.set(key, { result, expiresAt: Date.now() + ROTATION_MAP_TTL_MS, }); } // Exported for tests + diagnostics; not part of the public API surface. export function _getTokenRotationMapStats(): { size: number; entries: number } { cleanupRotationMap(); return { size: tokenRotationMap.size, entries: tokenRotationMap.size }; } export function _clearTokenRotationMap(): void { tokenRotationMap.clear(); }