// In-memory magic-link code store. Replace with Redis / DB for multi-instance deployments. export const pendingCodes = new Map< string, { code: string; expiresAt: number } >(); export const CODE_TTL_MS = 10 * 60 * 1000; // 10 minutes /** Per-email rate limit: max requests within the window. */ const RATE_LIMIT_WINDOW_MS = 15 * 60 * 1000; // 15 min const RATE_LIMIT_MAX = 3; const rateLimitMap = new Map(); /** Returns true if the email has exceeded the rate limit. */ export function isRateLimited(email: string): boolean { const now = Date.now(); const entry = rateLimitMap.get(email); if (!entry || now - entry.windowStart > RATE_LIMIT_WINDOW_MS) { rateLimitMap.set(email, { count: 1, windowStart: now }); return false; } entry.count++; return entry.count > RATE_LIMIT_MAX; } /** Purge expired codes and stale rate-limit entries. Call periodically. */ export function purgeExpired(): void { const now = Date.now(); Array.from(pendingCodes.entries()).forEach(([email, entry]) => { if (now > entry.expiresAt) pendingCodes.delete(email); }); Array.from(rateLimitMap.entries()).forEach(([email, entry]) => { if (now - entry.windowStart > RATE_LIMIT_WINDOW_MS) rateLimitMap.delete(email); }); }