/** * Simple in-memory rate limiter for PIN attempts. * * After MAX_FAILURES consecutive wrong PINs the account is locked for * LOCKOUT_MS. The counter resets on a successful unlock. */ const MAX_FAILURES = 5; const LOCKOUT_MS = 15 * 60 * 1000; // 15 minutes interface Entry { failures: number; lockedUntil: number | null; } const store = new Map(); function entry(userId: number): Entry { if (!store.has(userId)) store.set(userId, { failures: 0, lockedUntil: null }); return store.get(userId)!; } export function checkPinRateLimit(userId: number): void { const e = entry(userId); if (e.lockedUntil && Date.now() < e.lockedUntil) { const secondsLeft = Math.ceil((e.lockedUntil - Date.now()) / 1000); throw new RateLimitError(`Too many incorrect PINs. Try again in ${secondsLeft}s.`); } } export function recordPinFailure(userId: number): void { const e = entry(userId); e.failures += 1; if (e.failures >= MAX_FAILURES) { e.lockedUntil = Date.now() + LOCKOUT_MS; e.failures = 0; } } export function recordPinSuccess(userId: number): void { store.delete(userId); // reset counter on success } export class RateLimitError extends Error { constructor(message: string) { super(message); this.name = "RateLimitError"; } }