fix(security): restrict CORS to configured origin for session-auth routes

This commit is contained in:
coo1estguy
2026-09-19 15:25:46 +05:30
parent 7db1046582
commit b305d0ea03
3 changed files with 29 additions and 4 deletions
+3
View File
@@ -13,6 +13,9 @@ DATABASE_URL=postgres://platform:platform@localhost:5432/notifkit
# ── Auth ──────────────────────────────────────────────────────────────────────
# Bootstrap key. Full access to every project — mint per-project keys for apps.
ADMIN_API_KEY=my_super_secret_admin_key
# Allowed origins for CORS on session-authenticated admin endpoints (comma-separated).
# CORS_ORIGIN=https://dashboard.example.com
# ── Unsubscribe ───────────────────────────────────────────────────────────────
# Both are required before one-click unsubscribe headers are attached to mail.
+4
View File
@@ -70,6 +70,10 @@ export const baseConfigSchema = z.object({
* Defaults to false to prevent client-spoofed headers from bypassing rate limits.
*/
TRUST_PROXY: z.coerce.boolean().default(false),
/**
* Comma-separated list of allowed origins for CORS on session-authenticated admin routes.
*/
CORS_ORIGIN: z.string().optional(),
});
export type BaseConfig = z.infer<typeof baseConfigSchema>;
+22 -4
View File
@@ -329,8 +329,28 @@ export async function startApiServer() {
server.keepAliveTimeout = 5_000;
async function handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {
// Add CORS headers for browser clients (like the Next.js dashboard)
res.setHeader("Access-Control-Allow-Origin", "*");
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
const origin = req.headers["origin"] as string | undefined;
const allowedOrigins = (config.CORS_ORIGIN ?? "")
.split(",")
.map((s) => s.trim())
.filter(Boolean);
const isSessionRoute =
url.pathname.startsWith("/v1/auth/") ||
url.pathname === "/v1/projects" ||
url.pathname.startsWith("/v1/projects/") ||
url.pathname.startsWith("/v1/system/") ||
url.pathname.startsWith("/v1/dlq");
if (isSessionRoute) {
if (origin && allowedOrigins.includes(origin)) {
res.setHeader("Access-Control-Allow-Origin", origin);
res.setHeader("Vary", "Origin");
}
} else {
res.setHeader("Access-Control-Allow-Origin", "*");
}
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS");
res.setHeader(
"Access-Control-Allow-Headers",
@@ -343,8 +363,6 @@ export async function startApiServer() {
return;
}
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
if (url.pathname === "/admin" || url.pathname.startsWith("/admin/")) {
const handled = await handleAdminRequest(req, res, url);
if (handled) return;