| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566 |
- const WINDOW_MS = 15 * 60 * 1000; // 15 minutes
- const MAX_ATTEMPTS = 5;
- interface RateLimitEntry {
- count: number;
- resetAt: number;
- }
- const store = new Map<string, RateLimitEntry>();
- function cleanup() {
- const now = Date.now();
- for (const [key, entry] of store) {
- if (now >= entry.resetAt) {
- store.delete(key);
- }
- }
- }
- let cleanupInterval: ReturnType<typeof setInterval> | null = null;
- function ensureCleanupRunning() {
- if (cleanupInterval) return;
- cleanupInterval = setInterval(cleanup, 5 * 60 * 1000);
- if (typeof cleanupInterval === "object" && "unref" in cleanupInterval) {
- cleanupInterval.unref();
- }
- }
- export function checkRateLimit(key: string): {
- allowed: boolean;
- remaining: number;
- retryAfterMs: number;
- } {
- ensureCleanupRunning();
- const now = Date.now();
- const entry = store.get(key);
- if (!entry || now >= entry.resetAt) {
- store.set(key, { count: 1, resetAt: now + WINDOW_MS });
- return { allowed: true, remaining: MAX_ATTEMPTS - 1, retryAfterMs: 0 };
- }
- if (entry.count >= MAX_ATTEMPTS) {
- return {
- allowed: false,
- remaining: 0,
- retryAfterMs: entry.resetAt - now,
- };
- }
- entry.count += 1;
- return {
- allowed: true,
- remaining: MAX_ATTEMPTS - entry.count,
- retryAfterMs: 0,
- };
- }
- export function getClientIp(request: Request): string {
- const forwarded = request.headers.get("x-forwarded-for");
- if (forwarded) {
- return forwarded.split(",")[0].trim();
- }
- return "unknown";
- }
|