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>
49 lines
1.6 KiB
TypeScript
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();
|
|
};
|
|
}
|