Initial commit

This commit is contained in:
Developer
2026-02-06 21:44:04 -06:00
commit f85e93c7a6
151 changed files with 22916 additions and 0 deletions

94
lib/auth/rate-limit.ts Normal file
View File

@@ -0,0 +1,94 @@
/**
* Simple in-memory rate limiter for login attempts
* Tracks login attempts per email address
*/
interface RateLimitEntry {
attempts: number;
resetTime: number;
}
const loginAttempts = new Map<string, RateLimitEntry>();
const ATTEMPT_LIMIT = 5;
const TIME_WINDOW_MS = 15 * 60 * 1000; // 15 minutes
export function checkRateLimit(email: string): {
allowed: boolean;
attempts: number;
remainingTime: number;
} {
const now = Date.now();
const entry = loginAttempts.get(email);
// No previous attempts
if (!entry) {
loginAttempts.set(email, {
attempts: 1,
resetTime: now + TIME_WINDOW_MS,
});
return {
allowed: true,
attempts: 1,
remainingTime: TIME_WINDOW_MS,
};
}
// Window has expired
if (now > entry.resetTime) {
loginAttempts.set(email, {
attempts: 1,
resetTime: now + TIME_WINDOW_MS,
});
return {
allowed: true,
attempts: 1,
remainingTime: TIME_WINDOW_MS,
};
}
// Still within the window
const remainingTime = entry.resetTime - now;
const allowed = entry.attempts < ATTEMPT_LIMIT;
if (!allowed) {
// Still increment attempts to track that we rejected this attempt
return {
allowed: false,
attempts: entry.attempts,
remainingTime,
};
}
// Increment attempts
entry.attempts++;
return {
allowed: true,
attempts: entry.attempts,
remainingTime,
};
}
/**
* Record a failed login attempt (call this on invalid credentials)
* This increments the counter without checking the limit
*/
export function recordFailedAttempt(email: string): void {
const now = Date.now();
const entry = loginAttempts.get(email);
if (!entry || now > entry.resetTime) {
loginAttempts.set(email, {
attempts: 1,
resetTime: now + TIME_WINDOW_MS,
});
} else {
entry.attempts++;
}
}
/**
* Clear all rate limit entries (useful for testing or admin reset)
*/
export function clearRateLimits(): void {
loginAttempts.clear();
}