41 lines
1.1 KiB
TypeScript
41 lines
1.1 KiB
TypeScript
const WINDOW_MS = 60_000;
|
|
const MAX_CARD_HITS = 60;
|
|
const MAX_SIGNUP_HITS = 10;
|
|
|
|
const cardHitsByIp = new Map<string, number[]>();
|
|
const signupHitsByIp = new Map<string, number[]>();
|
|
|
|
function checkLimit(
|
|
store: Map<string, number[]>,
|
|
ip: string,
|
|
maxHits: number,
|
|
): { allowed: true } | { allowed: false; retryAfterSeconds: number } {
|
|
const now = Date.now();
|
|
const cutoff = now - WINDOW_MS;
|
|
const prev = store.get(ip) ?? [];
|
|
const recent = prev.filter((t) => t > cutoff);
|
|
if (recent.length >= maxHits) {
|
|
const retryAfterSeconds = Math.max(
|
|
1,
|
|
Math.ceil((recent[0]! + WINDOW_MS - now) / 1000),
|
|
);
|
|
store.set(ip, recent);
|
|
return { allowed: false, retryAfterSeconds };
|
|
}
|
|
recent.push(now);
|
|
store.set(ip, recent);
|
|
return { allowed: true };
|
|
}
|
|
|
|
export function checkPublicCardRateLimit(
|
|
ip: string,
|
|
): { allowed: true } | { allowed: false; retryAfterSeconds: number } {
|
|
return checkLimit(cardHitsByIp, ip, MAX_CARD_HITS);
|
|
}
|
|
|
|
export function checkPublicSignupRateLimit(
|
|
ip: string,
|
|
): { allowed: true } | { allowed: false; retryAfterSeconds: number } {
|
|
return checkLimit(signupHitsByIp, ip, MAX_SIGNUP_HITS);
|
|
}
|