Production splits web (descrybe.io) and API (api.descrybe.io). The session cookie was host-only for api.descrybe.io, so the browser never sent it to the web host. The /admin SvelteKit SSR gate (fetchMeStaff in +layout.server.ts) forwards the incoming cookie header to /api/auth/me — with no cookie to forward it always got 401 and bounced every successful login back to /login?next=/admin (login POST 200, /me 200 from the browser, /me 401 from the web server). New SESSION_COOKIE_DOMAIN env (default empty = host-only, local dev unchanged) sets the session cookie Domain attribute; set it to the parent domain (descrybe.io) in production so both hosts receive the cookie. Leading dot is normalized away. CSRF needs no change — it already seeds cross-origin via the X-CSRF-Token response header. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
167 lines
8.4 KiB
Bash
167 lines
8.4 KiB
Bash
# Descrybe v2 - REQUIRED bootstrap env only.
|
|
# Copy to repo-root `.env` (single source of truth). Do not create apps/api/.env.
|
|
# Product secrets (OpenAI, Stripe, mail, Pinecone, store connectors)
|
|
# belong in the admin dashboard -- do NOT put them here:
|
|
# platform: /admin/settings (stripe.* , feeds.private_url_allowlist)
|
|
# tenant: /integrations/ai , /integrations/email , /stores
|
|
# EPREL enrichment is on by default for all plans (public EU API). No activation
|
|
# step. Optional kill-switch only: EPREL_ENABLED=false here, or eprel.enabled=false
|
|
# under /admin/settings.
|
|
#
|
|
# How apps load env:
|
|
# - Go API/worker/mailhooks: config.Load() loads this monorepo-root `.env`
|
|
# (never overrides already-set process env). Override path: DOTENV_PATH=...
|
|
# - npm run dev:api / scripts/run-in-dir.mjs / run-api.ps1 / migrate.*: same root `.env`
|
|
# - SvelteKit: Vite envDir + kit.env.dir = monorepo root; PUBLIC_API_URL (+ optional PUBLIC_CSRF_COOKIE_NAME)
|
|
#
|
|
# Generate secrets locally (never commit real values):
|
|
# openssl rand -hex 32
|
|
|
|
# Postgres (required). Default matches docker-compose local DB on :5433.
|
|
DATABASE_URL=postgres://descrybe:descrybe@localhost:5433/descrybe?sslmode=disable
|
|
|
|
# development | staging | production
|
|
# Production fails closed: SESSION_SECURE=true, https WEB_ORIGIN, APP_ENCRYPTION_KEY,
|
|
# TOKEN_SIGNING_SECRET (see config.Load validate).
|
|
APP_ENV=development
|
|
|
|
# API listen address (npm run dev:api forces :28471 so a stale local .env cannot bind :8080)
|
|
HTTP_ADDR=:28471
|
|
|
|
# Optional Prometheus scrape (no secrets). Docs: docs/production-readiness.md#ops-prometheus-scrape
|
|
# Examples: deploy/prometheus/scrape.example.yml + alerts.example.yml
|
|
# METRICS_PUBLIC=1 # prod only: expose /metrics beyond loopback (private VIP/mesh only)
|
|
# METRICS_ADDR=127.0.0.1:9091 # worker-only listener for sync_* series
|
|
|
|
# Background worker — included in `npm run dev` (api+web+worker).
|
|
# Also: npm run dev:worker | make worker | .\scripts\run-api.ps1 worker
|
|
# Required for processing jobs, Woo/Shopify claim, support AI auto, billing, and GET /readyz.
|
|
|
|
# Browser origin for CORS / cookies (must be https + non-loopback in production).
|
|
# Dev web is Vite :28472 (strictPort). npm run dev forces matching WEB_ORIGIN / PUBLIC_API_URL.
|
|
# Local: API also accepts the localhost/127.0.0.1 twin for credentialed CORS (same port).
|
|
WEB_ORIGIN=http://localhost:28472
|
|
|
|
# Public API origin (web PUBLIC_API_URL should match; empty web = same-origin + Vite proxy)
|
|
PUBLIC_API_URL=http://localhost:28471
|
|
|
|
# Session cookie Secure flag (required true when APP_ENV=production|prod).
|
|
# Keep false on http://localhost — Secure cookies are dropped by the browser on HTTP.
|
|
SESSION_SECURE=false
|
|
|
|
# Browser smoke (codehelper site=local-descrybe). Never commit a real password.
|
|
# Set to the local demo password from docs/demo-user.md (a1-primary / demo),
|
|
# OR store it out-of-repo: printf '%s' "$DESCRYBE_SMOKE_PASS" | codehelper connections set-secret --name local-descrybe
|
|
# (then password_ref=secret). Unset/empty -> headless Sign-in submits no password -> /api/auth/me 401.
|
|
# DESCRYBE_SMOKE_PASS=
|
|
|
|
# Session / invite token HMAC. Required in production. openssl rand -hex 32
|
|
TOKEN_SIGNING_SECRET=
|
|
|
|
# AES key for credentials at rest (Woo/email/AI BYOK ciphertext).
|
|
# Required in production. Prefer this over legacy CREDENTIALS_ENCRYPTION_KEY.
|
|
# openssl rand -hex 32
|
|
APP_ENCRYPTION_KEY=
|
|
|
|
# --- Production host (uncomment / set on the server; setup --production helps) ---
|
|
# APP_ENV=production
|
|
# SESSION_SECURE=true
|
|
# WEB_ORIGIN=https://descrybe.io
|
|
# PUBLIC_API_URL=https://api.descrybe.io
|
|
# HTTP_ADDR=127.0.0.1:28471
|
|
# TRUSTED_PROXIES=127.0.0.1
|
|
# DATABASE_URL=postgres://descrybe:CHANGE_ME@127.0.0.1:5432/descrybe?sslmode=disable
|
|
# TOKEN_SIGNING_SECRET and APP_ENCRYPTION_KEY: leave empty and run
|
|
# npm run setup
|
|
# to generate (or: openssl rand -hex 32).
|
|
|
|
# Platform invite / forgot-password mail (optional env; prefer /admin/settings).
|
|
# EMAIL_DRY_RUN defaults true when unset (safe). Live send needs false + SMTP.
|
|
# EMAIL_DRY_RUN=true
|
|
# SMTP_ENABLED=false
|
|
# SMTP_HOST=
|
|
# SMTP_PORT=587
|
|
# SMTP_USER=
|
|
# SMTP_PASSWORD=
|
|
# SMTP_FROM=noreply@descrybe.io
|
|
|
|
# Cutover / ops toggles (optional)
|
|
# HYPERCARE_MODE=false
|
|
# STRIPE_MOCK=false
|
|
# MAINTENANCE_MODE=false
|
|
# READ_ONLY_MODE=false
|
|
|
|
# --- Optional bootstrap (safe defaults in code; uncomment to override) ---
|
|
# TRUSTED_PROXIES - hop-1 reverse-proxy / LB peers only (CIDR or IP, comma-separated).
|
|
# When set, TrustedRealIP rewrites RemoteAddr from X-Forwarded-For / X-Real-IP only if
|
|
# the TCP peer is on this allowlist. Empty (default) ignores client IP headers (safe
|
|
# without a proxy; required behind CDN/LB so auth IP RPM + login lockout key correctly).
|
|
# TRUSTED_PROXIES=127.0.0.1,10.0.0.0/8
|
|
# RATE_LIMIT_REPLICAS=1
|
|
# RATE_LIMIT_MULTI_REPLICA=false
|
|
# RATE_LIMIT_BACKEND=memory
|
|
# ASSUMPTION (Product 10): in-process rate limits + email login lockout are OK on a single
|
|
# API instance. Multi-replica cutover = edge/WAF hard global RPM (see deploy/examples/edge-rate-limit.md).
|
|
# Optional RATE_LIMIT_REPLICAS divides HTTP middleware caps only (not lockout/StartLimiter/AI/email)
|
|
# — not a shared store. RATE_LIMIT_BACKEND=redis|postgres is docs-only and forced to memory.
|
|
# SESSION_COOKIE_NAME=descrybe_session
|
|
# Session cookie Domain attribute. Empty = host-only (localhost / same-host).
|
|
# REQUIRED when web + api run on sibling subdomains (descrybe.io + api.descrybe.io):
|
|
# set the parent domain so the browser also sends the session cookie to the web
|
|
# host — SvelteKit SSR gates (/admin) forward it to /api/auth/me and otherwise
|
|
# always see 401 (login loops back to /login?next=...).
|
|
# SESSION_COOKIE_DOMAIN=descrybe.io
|
|
# CSRF_COOKIE_NAME=descrybe_csrf
|
|
# PUBLIC_CSRF_COOKIE_NAME=descrybe_csrf
|
|
# SESSION_IDLE_HOURS=24
|
|
# UPLOAD_DIR=data/uploads
|
|
# MAINTENANCE_MODE=false
|
|
# READ_ONLY_MODE=false
|
|
# CREDENTIALS_ENCRYPTION_KEY= # legacy alias for APP_ENCRYPTION_KEY
|
|
|
|
# --- Locales (not process-env; listed for agents/ops) ---
|
|
# UI dashboard locales (browser localStorage key descrybe-ui-locale; default en):
|
|
# en, es, fr, de, it, pt, nl, pl, ja
|
|
# Source: apps/web/src/lib/i18n/locales.ts (+ message catalogs under lib/i18n/messages)
|
|
# Content / AI language (companies.language via /settings Content Language; default en):
|
|
# en, fr, de, es, it, nl, pt, pl, cs, sk, hu, ro, bg, hr, sl, sv, da, fi, el, et, lv, lt, mt, ga, ja
|
|
# Sources: apps/web/src/lib/content-languages.ts , apps/api/internal/company/language.go
|
|
# Optional EPREL fiche PDF language (prefer /admin/settings eprel.fiche_language):
|
|
# EPREL_FICHE_LANGUAGE=EN
|
|
|
|
# --- Prefer dashboard (optional env fallback still accepted by resolvers) ---
|
|
# Platform /admin/settings (GET/PUT /api/admin/settings -> values.*):
|
|
# stripe.* , feeds.private_url_allowlist
|
|
# Optional EPREL overrides (not required - enrichment defaults on):
|
|
# eprel.enabled=false (or EPREL_ENABLED=false in this file) to disable
|
|
# eprel.base_url / eprel.timeout / eprel.fiche_language if tuning
|
|
# OPENAI_*/RESEND_*/SMTP_* may still work as process-env fallbacks; prefer UI.
|
|
# PROCESSING_*, DB_* pool knobs: code defaults; set in process env only if tuning.
|
|
# MIGRATE_MYSQL_DSN: migrator CLI only - not API boot.
|
|
|
|
# --- LLM test provider (optional; no production secrets) ---
|
|
# Tiny OpenAI-compatible stub for CI/local processing proofs. See docs/mock-llm.md.
|
|
# Start (separate terminal):
|
|
# cd apps/api && go run ./cmd/mock-llm -addr 127.0.0.1:18767
|
|
# Stub-only knobs (read by mock-llm process, not by Descrybe API):
|
|
# MOCK_LLM_API_KEY=local-test
|
|
# MOCK_LLM_MODEL=mock-llm
|
|
# Platform Completer fallback (root .env - restart api + worker after change):
|
|
# OPENAI_API_KEY=local-test
|
|
# OPENAI_BASE_URL=http://127.0.0.1:18767/v1
|
|
# OPENAI_MODEL=mock-llm
|
|
# PROCESSING_RPM=60
|
|
# PROCESSING_MAX_RETRIES=3
|
|
# Prefer tenant /integrations/ai (custom base + key) over process env when possible.
|
|
# Do NOT put real OpenAI/Green Chat secrets here - use dashboard or a private root .env.
|
|
# Verify translation + processing: docs/mock-llm.md ("Translation + processing verification").
|
|
|
|
# --- Web analytics (optional; SvelteKit PUBLIC_* from this root .env) ---
|
|
# Google Tag Manager container ID. When unset/empty, no GTM/GA tags load.
|
|
# Consent Mode v2 defaults to denied until the cookie banner grants categories.
|
|
# Setup: create GA4 property → GTM web container → GA4 Configuration tag with
|
|
# Consent Settings (require analytics_storage; ad_* for ads) → publish.
|
|
# SPA page views: Custom Event trigger `page_view` from the app. Do NOT also
|
|
# enable GTM History Change / GA4 enhanced measurement page_views (double count).
|
|
# PUBLIC_GTM_ID=GTM-XXXXXXX
|