Initial push

This commit is contained in:
2026-03-08 15:49:34 +01:00
parent 8da12bb7d1
commit 47127f276d
101 changed files with 13844 additions and 8 deletions

59
lib/security/rateLimit.ts Normal file
View File

@@ -0,0 +1,59 @@
/**
* In-memory rate limiter (per IP, per minute window).
* For production at scale, replace with Redis-backed solution.
*/
interface RateLimitEntry {
count: number;
windowStart: number;
}
const store = new Map<string, RateLimitEntry>();
const WINDOW_MS = 60_000; // 1 minute
// Cleanup stale entries every 5 minutes to prevent memory leaks
setInterval(() => {
const now = Date.now();
for (const [key, entry] of store.entries()) {
if (now - entry.windowStart > WINDOW_MS * 2) {
store.delete(key);
}
}
}, 300_000);
export function checkRateLimit(
ip: string,
limit: number = parseInt(process.env.RATE_LIMIT_RPM ?? "100"),
): { allowed: boolean; remaining: number; resetAt: number } {
const now = Date.now();
const entry = store.get(ip);
if (!entry || now - entry.windowStart > WINDOW_MS) {
store.set(ip, { count: 1, windowStart: now });
return { allowed: true, remaining: limit - 1, resetAt: now + WINDOW_MS };
}
if (entry.count >= limit) {
return {
allowed: false,
remaining: 0,
resetAt: entry.windowStart + WINDOW_MS,
};
}
entry.count++;
return {
allowed: true,
remaining: limit - entry.count,
resetAt: entry.windowStart + WINDOW_MS,
};
}
/** Extract real IP from request (handles proxies). */
export function getClientIp(request: Request): string {
return (
request.headers.get("x-real-ip") ??
request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ??
"unknown"
);
}