/** * Generate a PKCE code verifier + S256 challenge using the Web Crypto API. * Mirrors gjc's `packages/ai/src/utils/oauth/pkce.ts`. */ export async function generatePKCE(): Promise<{ verifier: string; challenge: string }> { const verifierBytes = new Uint8Array(96); crypto.getRandomValues(verifierBytes); const verifier = Buffer.from(verifierBytes).toString("base64url"); const data = new TextEncoder().encode(verifier); const hashBuffer = await crypto.subtle.digest("SHA-256", data); const challenge = Buffer.from(hashBuffer).toString("base64url"); return { verifier, challenge }; } /** Random hex CSRF state token. */ export function generateState(): string { const bytes = new Uint8Array(16); crypto.getRandomValues(bytes); return Array.from(bytes) .map(b => b.toString(16).padStart(2, "0")) .join(""); }