import { randomBytes, timingSafeEqual } from "crypto"; /** * Generate cryptographically secure state parameter for CSRF protection * * The state parameter is used to prevent CSRF attacks by ensuring the * callback request originated from our initiate request. * * @returns Random 32-byte hex string */ export function generateState(): string { return randomBytes(32).toString("hex"); } /** * Generate cryptographically secure session ID * * @returns Random 16-byte hex string */ export function generateSessionId(): string { return randomBytes(16).toString("hex"); } /** * Generate cryptographically secure nonce for ID token replay protection * * The nonce is included in the authorization request and must be present * in the ID token claims to prevent replay attacks. * * @returns Random 16-byte hex string */ export function generateNonce(): string { return randomBytes(16).toString("hex"); } /** * Validate state parameter using constant-time comparison * * Uses timing-safe comparison to prevent timing attacks that could * reveal information about the expected state value. * * @param providedState - State from callback request * @param expectedState - State from session * @returns true if states match, false otherwise */ export function validateState(providedState: string, expectedState: string): boolean { if (!providedState || !expectedState) { return false; } // Convert to buffers for constant-time comparison const providedBuffer = Buffer.from(providedState, "utf8"); const expectedBuffer = Buffer.from(expectedState, "utf8"); // Buffers must be same length for timingSafeEqual if (providedBuffer.length !== expectedBuffer.length) { return false; } try { return timingSafeEqual(providedBuffer, expectedBuffer); } catch (error) { // timingSafeEqual throws if lengths differ (shouldn't happen due to check above) return false; } }