Files

52 lines
1.9 KiB
TypeScript

export const MAX_IN_FLIGHT_INSPECTIONS = 4;
export const RATE_LIMIT_WINDOW_MS = 60_000;
export const RATE_LIMIT_MAX_REQUESTS = 30;
const MAX_TRACKED_CLIENTS = 1_024;
let inFlight = 0;
const requestWindows = new Map<string, number[]>();
export function tryAcquireInspection() {
if (inFlight >= MAX_IN_FLIGHT_INSPECTIONS) return null;
inFlight += 1;
let released = false;
return () => {
if (released) return;
released = true;
inFlight = Math.max(0, inFlight - 1);
};
}
export function clientKeyFromHeaders(headers: { get(name: string): string | null }) {
if (process.env.CERTSCOPE_TRUST_PROXY !== "true") return "direct";
const forwarded = headers.get("x-forwarded-for") ?? headers.get("x-real-ip") ?? "unknown";
const client = forwarded.split(",", 1)[0]?.trim().slice(0, 128) || "unknown";
return "proxy:" + client;
}
export function checkRateLimit(key: string, now = Date.now()) {
const windowStart = now - RATE_LIMIT_WINDOW_MS;
const previous = requestWindows.get(key) ?? [];
const recent = previous.filter((timestamp) => timestamp > windowStart);
if (recent.length >= RATE_LIMIT_MAX_REQUESTS) {
const retryAfterMs = Math.max(1_000, (recent[0] ?? now) + RATE_LIMIT_WINDOW_MS - now);
requestWindows.set(key, recent);
return { allowed: false, retryAfterSeconds: Math.ceil(retryAfterMs / 1_000) };
}
recent.push(now);
requestWindows.set(key, recent);
if (requestWindows.size > MAX_TRACKED_CLIENTS) {
for (const [client, timestamps] of requestWindows) {
if (!timestamps.some((timestamp) => timestamp > windowStart)) requestWindows.delete(client);
if (requestWindows.size <= MAX_TRACKED_CLIENTS) break;
}
if (requestWindows.size > MAX_TRACKED_CLIENTS) {
const oldestClient = requestWindows.keys().next().value;
if (typeof oldestClient === "string" && oldestClient !== key) requestWindows.delete(oldestClient);
}
}
return { allowed: true, retryAfterSeconds: 0 };
}