/** * CSRF token utilities * Generates and validates CSRF tokens to prevent cross-site request forgery */ import crypto from 'crypto'; const CSRF_TOKEN_LENGTH = 32; const CSRF_TOKEN_EXPIRY = 3600000; // 1 hour in milliseconds interface TokenData { sessionId: string; expiresAt: number; } // In-memory token store (for production, use Redis or database) const tokenStore: Map = new Map(); // Cleanup expired tokens periodically setInterval(() => { const now = Date.now(); for (const [token, data] of tokenStore.entries()) { if (data.expiresAt < now) { tokenStore.delete(token); } } }, 300000); // Clean every 5 minutes /** * Generate a new CSRF token * @param sessionId - Unique identifier for the user session (e.g., cookie ID) * @returns The generated CSRF token */ export function generateCsrfToken(sessionId: string): string { const token = crypto.randomBytes(CSRF_TOKEN_LENGTH).toString('hex'); const expiresAt = Date.now() + CSRF_TOKEN_EXPIRY; tokenStore.set(token, { sessionId, expiresAt, }); return token; } /** * Validate a CSRF token * @param token - The token to validate * @param sessionId - The session ID to validate against * @returns True if token is valid, false otherwise */ export function validateCsrfToken(token: string | null | undefined, sessionId: string | null | undefined): boolean { if (!token || !sessionId) { return false; } const data = tokenStore.get(token); if (!data) { return false; } // Check expiry if (data.expiresAt < Date.now()) { tokenStore.delete(token); return false; } // Check session ID match if (data.sessionId !== sessionId) { return false; } // Token is valid - consume it (one-time use) tokenStore.delete(token); return true; } /** * Get token store size (for monitoring) */ export function getTokenStoreSize(): number { return tokenStore.size; }