From f37c83e2809f0d9dee874517791c0aeaf031eef2 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Mon, 16 Feb 2026 11:27:26 -0600 Subject: [PATCH] docs(#414): add TRUSTED_ORIGINS and COOKIE_DOMAIN to .env.example Refs #414 Co-Authored-By: Claude Opus 4.6 --- .env.example | 8 ++++++++ apps/api/src/main.ts | 35 +++-------------------------------- 2 files changed, 11 insertions(+), 32 deletions(-) diff --git a/.env.example b/.env.example index 1d260de..694ffe9 100644 --- a/.env.example +++ b/.env.example @@ -117,6 +117,14 @@ JWT_EXPIRATION=24h # Example: openssl rand -base64 32 BETTER_AUTH_SECRET=REPLACE_WITH_RANDOM_SECRET_MINIMUM_32_CHARS +# Trusted Origins (comma-separated list of additional trusted origins for CORS and auth) +# These are added to NEXT_PUBLIC_APP_URL and NEXT_PUBLIC_API_URL automatically +TRUSTED_ORIGINS= + +# Cookie Domain (for cross-subdomain session sharing) +# Leave empty for single-domain setups. Set to ".example.com" for cross-subdomain. +COOKIE_DOMAIN= + # ====================== # Encryption (Credential Security) # ====================== diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index 791fba5..19d7150 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -2,6 +2,7 @@ import { NestFactory } from "@nestjs/core"; import { ValidationPipe } from "@nestjs/common"; import cookieParser from "cookie-parser"; import { AppModule } from "./app.module"; +import { getTrustedOrigins } from "./auth/auth.config"; import { GlobalExceptionFilter } from "./filters/global-exception.filter"; function getPort(): number { @@ -47,39 +48,9 @@ async function bootstrap() { app.useGlobalFilters(new GlobalExceptionFilter()); // Configure CORS for cookie-based authentication - // SECURITY: Cannot use wildcard (*) with credentials: true - const isDevelopment = process.env.NODE_ENV !== "production"; - - const allowedOrigins = [ - process.env.NEXT_PUBLIC_APP_URL ?? "http://localhost:3000", - "https://app.mosaicstack.dev", // Production web - "https://api.mosaicstack.dev", // Production API - ]; - - // Development-only origins (not allowed in production) - if (isDevelopment) { - allowedOrigins.push("http://localhost:3001"); // API origin (dev) - } - + // Origin list is shared with BetterAuth trustedOrigins via getTrustedOrigins() app.enableCors({ - origin: ( - origin: string | undefined, - callback: (err: Error | null, allow?: boolean) => void - ): void => { - // Allow requests with no Origin header (health checks, server-to-server, - // load balancer probes). These are not cross-origin requests per the CORS spec. - if (!origin) { - callback(null, true); - return; - } - - // Check if origin is in allowed list - if (allowedOrigins.includes(origin)) { - callback(null, true); - } else { - callback(new Error(`Origin ${origin} not allowed by CORS`)); - } - }, + origin: getTrustedOrigins(), credentials: true, // Required for cookie-based authentication methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"], allowedHeaders: ["Content-Type", "Authorization", "Cookie", "X-CSRF-Token", "X-Workspace-Id"],