import "dotenv/config"; import { Hono } from "hono"; import { serveStatic } from "hono/bun"; import { bodyLimit } from "hono/body-limit"; import { cors } from "hono/cors"; import { logger } from "hono/logger"; import {authConfig, authMiddleware, authRoutes} from './auth'; import eks from "./routes/eks"; import ec2 from "./routes/ec2"; import secretsmanager from "./routes/secretsmanager"; import clouds from "./routes/clouds"; import msk from "./routes/msk"; import mskConnect from "./routes/mskConnect"; import ses from "./routes/ses"; import elasticache from "./routes/elasticache"; const app = new Hono(); const authentication = authConfig(); // Secrets Manager and KMS crypto routes handle sensitive values with server-side // AWS credentials, so an unrestricted `cors()` would let any web page in the // browser drive them cross-origin. Restrict CORS to trusted origins for those // routes and keep the permissive default elsewhere. In production the frontend // is served from the same origin (see serveStatic below), so same-origin requests // are unaffected; cross-origin callers must be explicitly allow-listed. // // A single CORS middleware handles both cases: stacking two `cors()` calls // would either short-circuit the OPTIONS preflight in the wrong handler or let // the later one overwrite `Access-Control-Allow-Origin` on real requests. const trustedOrigins = ( process.env.CORS_ALLOWED_ORIGINS ?? "http://localhost:3000" ) .split(",") .map((origin) => origin.trim()) .filter(Boolean); app.use( "*", cors({ origin: (origin, c) => isSensitiveCredentialRoute(c.req.path) ? trustedOrigins.includes(origin) ? origin : null : "*", }), ); app.use("*", logger()); // Sign-in endpoints read the body before a session exists, so they get a small // limit; uploads (S3 objects, Lambda ZIPs sent as base64, Lightsail bundles) keep // Bun's default 128 MB ceiling. app.use("/api/*", (c, next) => bodyLimit({ maxSize: requestBodyLimit(c.req.path), onError: (c) => c.json({ error: "Request body is too large" }, 413), })(c, next), ); app.use("*", authMiddleware(authentication)); app.use("*", authRoutes(authentication)); app.get("/api/health", (c) => c.json({ status: "ok" })); app.route("/api/eks", eks); app.route("/api/ec2", ec2); app.route("/api/secretsmanager", secretsmanager); app.route("/api/clouds", clouds); app.route("/api/msk", msk); app.route("/api/msk-connect", mskConnect); app.route("/api/ses", ses); app.route("/api/elasticache", elasticache); app.all("/api/*", (c) => c.json({ error: "Not found" }, 404)); // Serve static frontend files when public/ directory is present (production) app.use("*", serveStatic({ root: "./public" })); app.get("*", serveStatic({ path: "./public/index.html" })); const port = Number(process.env.PORT ?? 4501); export default { port, fetch: app.fetch }; function requestBodyLimit(path: string): number { if (path.startsWith("/api/auth/")) return 16 * 1024; if ( /^\/api\/clouds\/[^/]+\/services\/[^/]+\/resources(?:\/[^/]+(?:\/object|\/static-files|\/node-application)?)?$/.test(path) ) { return 128 * 1024 * 1024; } return 10 * 1024 * 1024; } function isSensitiveCredentialRoute(path: string): boolean { return ( path.startsWith("/api/secretsmanager") || /^\/api\/clouds\/[^/]+\/services\/kms\/resources\/[^/]+\/(?:encrypt|decrypt)$/.test(path) ); }