Skip to content
File

Blob: src/worker/middleware/rate-limit.ts

typescript38 lines
1import { createMiddleware } from "hono/factory";
2import type { users } from "@/worker/db/d1/schema";
3import { createLogger } from "@/worker/lib/logger";
4import { CF_IP_HEADER } from "@/worker/lib/constants";
5import { isLocalRequestUrl } from "@/worker/http";
6 
7const log = createLogger("rate-limit");
8 
9export function rateLimit(binding: "RL_AUTH" | "RL_API" | "RL_AI") {
10 return createMiddleware<{
11 Bindings: Env;
12 Variables: {
13 user: typeof users.$inferSelect | null;
14 };
15 }>(async (c, next) => {
16 // Local dev and browser-driven E2E run through a single loopback IP and can
17 // legitimately make bursts of auth/refresh calls during page bootstrap.
18 // Keep production rate limits intact while avoiding flaky local lockouts.
19 if (isLocalRequestUrl(c.req.url)) {
20 await next();
21 return;
22 }
23 
24 const key =
25 binding === "RL_AUTH"
26 ? (c.req.header(CF_IP_HEADER) ?? "unknown")
27 : (c.get("user")?.id ?? c.req.header(CF_IP_HEADER) ?? "unknown");
28 
29 const { success } = await c.env[binding].limit({ key });
30 if (!success) {
31 log.info("rate_limit_exceeded", { binding, key, path: c.req.path });
32 return c.json({ error: "rate_limited", message: "Too many requests" }, 429);
33 }
34 
35 await next();
36 });
37}