Files
Spanglish/backend/src/lib/rateLimit.ts
T
MichilisandCursor 613bd7be1d Refactor monolithic modules and harden booking, email, and auth infrastructure.
Split oversized frontend API client, email service, and admin/booking pages into focused modules while preserving import surfaces, and add Redis-backed queues, stale booking cleanup, stronger auth, and scale deployment configs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-25 07:12:59 +00:00

49 lines
1.6 KiB
TypeScript

import { Context } from 'hono';
import { getRateLimiter } from './stores/rateLimiter.js';
/**
* Rate limiting helpers.
*
* The actual counting is delegated to a pluggable rate limiter (in-memory by
* default, Redis-backed when REDIS_URL is set) so limits are enforced either
* per instance (single-instance deployments) or across all instances
* (horizontal scaling). See lib/stores/rateLimiter.ts.
*/
/** Best-effort client IP extraction (honours common reverse-proxy headers). */
export function getClientIp(c: Context): string {
const forwarded = c.req.header('x-forwarded-for');
if (forwarded) return forwarded.split(',')[0].trim();
return c.req.header('x-real-ip') || 'unknown';
}
/**
* Consume one unit against a key. Returns whether the request is allowed and,
* when blocked, how many seconds until the window resets.
*/
export function consumeRateLimit(
key: string,
max: number,
windowMs: number
): Promise<{ allowed: boolean; retryAfter?: number }> {
return getRateLimiter().consume(key, max, windowMs);
}
/**
* Hono middleware factory that rate-limits by client IP.
* Use a distinct `prefix` per endpoint group so unrelated routes don't share a bucket.
*/
export function rateLimitMiddleware(opts: { max: number; windowMs: number; prefix: string }) {
return async (c: Context, next: () => Promise<void>) => {
const ip = getClientIp(c);
const result = await consumeRateLimit(`${opts.prefix}:${ip}`, opts.max, opts.windowMs);
if (!result.allowed) {
return c.json(
{ error: 'Too many requests. Please try again later.', retryAfter: result.retryAfter },
429
);
}
await next();
};
}