You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
45 lines
1.0 KiB
45 lines
1.0 KiB
// In-memory rate limiter keyed by IP
|
|
const loginAttempts = new Map<
|
|
string,
|
|
{ count: number; resetAt: number }
|
|
>();
|
|
|
|
const WINDOW_MS = 60_000; // 1 minute window
|
|
const MAX_ATTEMPTS = 5; // max 5 attempts per window
|
|
const LOCKOUT_MS = 15 * 60_000; // 15 minute lockout
|
|
|
|
function cleanupExpired(now: number): void {
|
|
for (const [ip, entry] of loginAttempts.entries()) {
|
|
if (now > entry.resetAt) {
|
|
loginAttempts.delete(ip);
|
|
}
|
|
}
|
|
}
|
|
|
|
export function checkRateLimit(ip: string): {
|
|
allowed: boolean;
|
|
retryAfterMs: number;
|
|
} {
|
|
const now = Date.now();
|
|
cleanupExpired(now);
|
|
const entry = loginAttempts.get(ip);
|
|
|
|
if (!entry || now > entry.resetAt) {
|
|
loginAttempts.set(ip, { count: 1, resetAt: now + WINDOW_MS });
|
|
return { allowed: true, retryAfterMs: 0 };
|
|
}
|
|
|
|
if (entry.count >= MAX_ATTEMPTS) {
|
|
return {
|
|
allowed: false,
|
|
retryAfterMs: entry.resetAt - now,
|
|
};
|
|
}
|
|
|
|
entry.count++;
|
|
return { allowed: true, retryAfterMs: 0 };
|
|
}
|
|
|
|
export function clearRateLimit(ip: string): void {
|
|
loginAttempts.delete(ip);
|
|
}
|