Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
44 lines
1.2 KiB
TypeScript
44 lines
1.2 KiB
TypeScript
import { redis } from '../redis.js';
|
|
|
|
export interface RateLimitResult {
|
|
limited: boolean;
|
|
retryAfter: number;
|
|
}
|
|
|
|
export async function checkRateLimit(
|
|
ip: string,
|
|
action: string,
|
|
max: number,
|
|
windowSeconds: number,
|
|
): Promise<RateLimitResult> {
|
|
const key = `rl:${action}:${ip}`;
|
|
const hits = await redis.incr(key);
|
|
if (hits === 1) await redis.expire(key, windowSeconds);
|
|
if (hits > max) {
|
|
const ttl = await redis.ttl(key);
|
|
return { limited: true, retryAfter: Math.max(ttl, 1) };
|
|
}
|
|
return { limited: false, retryAfter: 0 };
|
|
}
|
|
|
|
export async function checkRateLimitBySubject(
|
|
subject: string,
|
|
action: string,
|
|
max: number,
|
|
windowSeconds: number,
|
|
): Promise<RateLimitResult> {
|
|
const key = `rl:${action}:${subject}`;
|
|
const hits = await redis.incr(key);
|
|
if (hits === 1) await redis.expire(key, windowSeconds);
|
|
if (hits > max) {
|
|
const ttl = await redis.ttl(key);
|
|
return { limited: true, retryAfter: Math.max(ttl, 1) };
|
|
}
|
|
return { limited: false, retryAfter: 0 };
|
|
}
|
|
|
|
export function rateLimitResponse(reply: import('fastify').FastifyReply, retryAfter: number) {
|
|
reply.header('Retry-After', String(retryAfter));
|
|
return reply.code(429).send({ error: 'rate_limited', retryAfter });
|
|
}
|