diff --git a/.env.example b/.env.example index 0c785543..3413c586 100644 --- a/.env.example +++ b/.env.example @@ -82,7 +82,8 @@ OBJECT_STORAGE_MEDIA_PUBLIC_BASE_URL=http://localhost:9000/media GOOGLE_CLIENT_ID= GOOGLE_CLIENT_SECRET= -# Cookie domain for cross-subdomain auth (e.g. .yourdomain.com) +# Cookie domain for auth. Optional for Docker (falls back to DASHBOARD_ORIGIN). +# For Railway: set to your dashboard domain (e.g. app.yourdomain.com) when API and dashboard use different subdomains. AUTH_COOKIE_DOMAIN= # Dashboard domain routing diff --git a/apps/docs/content/docs/quickstart/self-hosting.mdx b/apps/docs/content/docs/quickstart/self-hosting.mdx index ab237ac7..38e40f4c 100644 --- a/apps/docs/content/docs/quickstart/self-hosting.mdx +++ b/apps/docs/content/docs/quickstart/self-hosting.mdx @@ -114,7 +114,7 @@ Use `./run-docker-full-stack.sh --no-minio` to exclude MinIO if you do not need | Variable | Description | |----------|-------------| | `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET` | Google OAuth | -| `AUTH_COOKIE_DOMAIN` | Cross-subdomain cookies (e.g. `.yourdomain.com`) | +| `AUTH_COOKIE_DOMAIN` | Railway: dashboard domain when API/dashboard use different subdomains. Docker: optional (falls back to `DASHBOARD_ORIGIN`) | | `PRIVATE_APP_HOST`, `PRIVATE_APP_SUBDOMAINS` | Multi-subdomain routing | | `UNSPLASH_API_KEY` | Unsplash image search | | `LICENSE_KEY` | Enterprise features (SSO, token-auth, no-tracking) | @@ -188,10 +188,12 @@ PUBLIC_SERVER_URL="https://api.yourdomain.com" TRUSTED_ORIGINS="https://api.yourdomain.com,https://app.yourdomain.com" BETTER_AUTH_SECRET="random-secure-key" AUTH_BEARER_TOKEN="some-key" -AUTH_COOKIE_DOMAIN="yourdomain.com" PRIVATE_SERVER_KEY="same-key-for-api-and-dashboard" PUBLIC_IS_SELFHOSTED="true" +# Required for auth cookies when API and dashboard use different subdomains — set to your dashboard domain, e.g. app.yourdomain.com +AUTH_COOKIE_DOMAIN="app.yourdomain.com" + # Email (configure one) SMTP_HOST= SMTP_PORT= @@ -207,6 +209,10 @@ CLOUDFLARE_BUCKET_DOMAIN= CLOUDFLARE_IMAGE_BUCKET_DOMAIN= ``` + + When API and dashboard use different subdomains, set `AUTH_COOKIE_DOMAIN` to your dashboard domain (e.g. `app.yourdomain.com`). Without it, Better Auth cookies will not be set and login will fail. Docker self-hosting falls back to `DASHBOARD_ORIGIN` when this is absent. + + Optional: add `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET` for OAuth, `UNSPLASH_API_KEY` for image search, or `LICENSE_KEY` for enterprise features. ### cio-dashboard diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 16577bc9..5f7efab5 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -107,8 +107,9 @@ services: NODE_ENV: production PORT: 3081 PUBLIC_SERVER_URL: ${PUBLIC_SERVER_URL:-http://localhost:3081} - TRUSTED_ORIGINS: ${TRUSTED_ORIGINS:-http://localhost:3082,http://localhost:5173} + TRUSTED_ORIGINS: ${TRUSTED_ORIGINS:-http://localhost:3082} AUTH_COOKIE_DOMAIN: ${AUTH_COOKIE_DOMAIN:-} + DASHBOARD_ORIGIN: ${DASHBOARD_ORIGIN:-http://localhost:3082} DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@postgres:5432/${POSTGRES_DB:-classroomio} PRIVATE_DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@postgres:5432/${POSTGRES_DB:-classroomio} REDIS_URL: redis://redis:6379 diff --git a/docker/docs/SELF_HOST.md b/docker/docs/SELF_HOST.md index 9ec54831..2c958695 100644 --- a/docker/docs/SELF_HOST.md +++ b/docker/docs/SELF_HOST.md @@ -26,6 +26,7 @@ All Docker services read from a single root `.env` file. See [`.env.example`](.. Key points: - **Required:** `PUBLIC_SERVER_URL`, `TRUSTED_ORIGINS`, `BETTER_AUTH_SECRET`, `PUBLIC_IS_SELFHOSTED=true`, `ALLOWED_EXTERNAL_DOMAINS`, `DASHBOARD_ORIGIN`. +- **Auth cookies:** When `AUTH_COOKIE_DOMAIN` is absent, the API falls back to `DASHBOARD_ORIGIN` (hostname). - **Auto-generated:** `AUTH_BEARER_TOKEN`, `PRIVATE_SERVER_KEY` (by `./run-docker-full-stack.sh`). - **Auto-configured:** All `MINIO_*` / `OBJECT_STORAGE_*` vars (by the startup script). - **Optional:** Email (SMTP or Zoho), Google OAuth, Unsplash, `LICENSE_KEY` (enterprise). diff --git a/packages/db/src/constants.ts b/packages/db/src/constants.ts index cf3f027c..6268a71c 100644 --- a/packages/db/src/constants.ts +++ b/packages/db/src/constants.ts @@ -1,7 +1,8 @@ const { TRUSTED_ORIGINS: TRUSTED_ORIGINS_STRING, PUBLIC_SERVER_URL, - AUTH_COOKIE_DOMAIN: AUTH_COOKIE_DOMAIN_STRING + AUTH_COOKIE_DOMAIN: AUTH_COOKIE_DOMAIN_STRING, + DASHBOARD_ORIGIN: DASHBOARD_ORIGIN_STRING } = process.env; const DEFAULT_TRUSTED_ORIGINS = [ @@ -30,4 +31,18 @@ export const TRUSTED_ORIGINS = TRUSTED_ORIGINS_STRING export const BASE_URL = PUBLIC_SERVER_URL || 'http://localhost:3002'; -export const AUTH_COOKIE_DOMAIN = AUTH_COOKIE_DOMAIN_STRING?.trim() || undefined; +function getAuthCookieDomain(): string | undefined { + const explicit = AUTH_COOKIE_DOMAIN_STRING?.trim(); + if (explicit) return explicit; + + if (!DASHBOARD_ORIGIN_STRING?.trim()) return undefined; + + try { + const url = new URL(DASHBOARD_ORIGIN_STRING.trim()); + return url.hostname || undefined; + } catch { + return undefined; + } +} + +export const AUTH_COOKIE_DOMAIN = getAuthCookieDomain();