import type { Context } from "jsr:@hono/hono@4"; import { rateLimitWrapper } from "./ratelimit.ts"; import { getAuthenticatedUser } from "./auth-session.ts"; // Middleware helper to get IP address export function getClientIp(c: Context): string { // Check for X-Real-IP first const realIp = c.req.header("x-real-ip"); if (realIp) { return realIp.trim(); } // Fallback to X-Forwarded-For, bounded to prevent memory exhaustion let forwardedFor = c.req.header("x-forwarded-for"); if (forwardedFor) { // Truncate to a max of 256 characters if (forwardedFor.length > 256) { forwardedFor = forwardedFor.substring(0, 256); } const parts = forwardedFor.split(","); // Extract the last untrusted hop (right-most IP) return parts[parts.length - 1].trim(); } // Fallback (might not be accurate behind proxy without X-Forwarded-For) return "unknown-ip"; } export const publicRateLimiter = async ( c: Context, next: () => Promise, ) => { const ip = getClientIp(c); const key = `ratelimit:public:${ip}`; const allowed = await rateLimitWrapper.checkRateLimit(key, 10, 60000); if (!allowed) { return c.json({ error: "Too Many Requests" }, 429); } await next(); }; export const adminRateLimiter = async ( c: Context, next: () => Promise, ) => { const auth = await getAuthenticatedUser(c); if (!auth) { return c.json({ error: "Missing or invalid session" }, 401); } const key = `ratelimit:admin:${auth.userId}`; const allowed = await rateLimitWrapper.checkRateLimit(key, 60, 60000); if (!allowed) { return c.json({ error: "Too Many Requests" }, 429); } // Stash userId in context so downstream routes can use it c.set("userId", auth.userId); await next(); }; // Also apply requireAdmin logic on the admin route by chaining it or exporting it. // We'll export requireAdmin here or use it directly in the admin routes.