- Extracts Auth, Registration, and Passkey routes into `server/routes/auth.ts`. - Extracts all Admin API endpoints into `server/routes/admin.ts`. - Extracts RPC Connect setup and mTLS listener into `server/rpc.ts`. - Extracts global rate limiters and IP helpers into `server/middleware.ts`. - Reduces `server/main.ts` purely to an entrypoint mounting orchestrator. - Ensures all existing tests and quality gates pass with zero regressions. Co-authored-by: mrteye <1945243+mrteye@users.noreply.github.com>
63 lines
1.9 KiB
TypeScript
63 lines
1.9 KiB
TypeScript
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<void>,
|
|
) => {
|
|
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<void>,
|
|
) => {
|
|
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.
|