interface Entry { count: number; windowStart: number; } function makeRateLimiter( maxAttempts: number, windowMs: number ) { const store = new Map(); return function check( ip: string ): { blocked: boolean } { const now = Date.now(); let entry = store.get( ip ); if ( !entry || now - entry.windowStart > windowMs ) entry = { count: 0, windowStart: now }; entry.count++; store.set( ip, entry ); return { blocked: entry.count > maxAttempts }; }; } // forgot-password — strict, with escalating delay to prevent email spam const FORGOT_WINDOW_MS = 20 * 60 * 1000; const FORGOT_MAX_ATTEMPTS = 20; const forgotStore = new Map(); function forgotDelayMs( count: number ): number { if ( count <= 5 ) return 5_000; if ( count <= 10 ) return 15_000; return 30_000; } export function checkForgotPasswordRate( ip: string ): { blocked: boolean; delay: number } { const now = Date.now(); let entry = forgotStore.get( ip ); if ( !entry || now - entry.windowStart > FORGOT_WINDOW_MS ) entry = { count: 0, windowStart: now }; entry.count++; forgotStore.set( ip, entry ); if ( entry.count > FORGOT_MAX_ATTEMPTS ) return { blocked: true, delay: 0 }; return { blocked: false, delay: forgotDelayMs( entry.count ) }; } // login — 10 attempts per 15 min, small delay after 5 const loginStore = new Map(); const LOGIN_WINDOW_MS = 15 * 60 * 1000; const LOGIN_MAX_ATTEMPTS = 10; function loginDelayMs( count: number ): number { if ( count <= 5 ) return 0; return 3_000; } export function checkLoginRate( ip: string ): { blocked: boolean; delay: number } { const now = Date.now(); let entry = loginStore.get( ip ); if ( !entry || now - entry.windowStart > LOGIN_WINDOW_MS ) entry = { count: 0, windowStart: now }; entry.count++; loginStore.set( ip, entry ); if ( entry.count > LOGIN_MAX_ATTEMPTS ) return { blocked: true, delay: 0 }; return { blocked: false, delay: loginDelayMs( entry.count ) }; } // register — 5 attempts per hour, no delay export const checkRegisterRate = makeRateLimiter( 5, 60 * 60 * 1000 ); export function sleep( ms: number ): Promise { return new Promise( resolve => setTimeout( resolve, ms ) ); }