File size: 1,290 Bytes
30cc31a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | // 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<string, { count: number; windowStart: number }>();
/** 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);
});
}
|