fix(security): a production server refuses to start until its keys, allowed origins and test login are right (REL-01, REL-03, SEC-04)

With NODE_ENV=production (always, in the Docker image) the API lists
every problem at once and does not start while any of these holds:
- OTP_MASTER_CODE is set: the fixed test login signs in any customer. A
  test server built from the production image must say so with
  TEST_SERVER_ALLOW_MASTER_CODE=yes.
- JWT_CUSTOMER_SECRET is missing, equal to JWT_SECRET, or under 32
  characters.
- JWT_SECRET is missing, the published dev key, or under 32 characters.
- CORS_ORIGINS is empty, `*`, or not full origins. It no longer falls
  back to allow-all.

Checked live: a misconfigured start lists all four problems; a correct
one starts, answers the allowed origin, and blocks any other.
docs/SERVER-SETUP-CHECKLIST.md is the operator's list. DELIVERY.md,
docker-compose.yml and .env.example are updated to match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
s.esmaeili
2026-10-08 17:00:17 +03:30
co-authored by Claude Opus 5.5
parent d91dcf197b
commit 9d3b5388ba
8 changed files with 256 additions and 42 deletions
+7 -5
View File
@@ -7,7 +7,9 @@ PORT=3000
# Customer app sessions (security hotfix stage 2).
# Separate from JWT_SECRET so an admin token can never validate as a customer
# token. Falls back to JWT_SECRET, then to a dev-only string, when unset.
# token. Falls back to JWT_SECRET, then to a dev-only string, when unset —
# development only: in production it is REQUIRED, at least 32 characters and
# different from JWT_SECRET, or the API refuses to start (REL-03).
JWT_CUSTOMER_SECRET=
# Customer token lifetime. The reference issued a token that never expired.
JWT_CUSTOMER_EXPIRES_IN=30d
@@ -53,10 +55,10 @@ ACCOUNTING_SECRET_KEY=
ACCOUNTING_WORKER=
# SEC-04 — browser origins allowed to call the API, comma-separated, exact
# (e.g. https://adm.example.ir,https://app.example.ir). Blank = any origin
# (development); in production a blank list prints a warning at boot.
# In production (NODE_ENV=production) JWT_SECRET is REQUIRED: the API refuses
# to start on the built-in development key.
# (e.g. https://adm.example.ir,https://app.example.ir). Blank = any origin,
# development only. In production (NODE_ENV=production) the API refuses to
# start until CORS_ORIGINS lists full origins and JWT_SECRET is a random value
# of at least 32 characters (SEC-04). See docs/SERVER-SETUP-CHECKLIST.md.
CORS_ORIGINS=
# SEC-05 — the seed runs once on a fresh database at boot. In production it