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:
co-authored by
Claude Opus 5.5
parent
d91dcf197b
commit
9d3b5388ba
+14
-5
@@ -25,10 +25,17 @@ DATABASE_URL=postgresql://postgres:<the same password>@db:5432/goldbank?schema=p
|
||||
|
||||
# Auth — two DIFFERENT random values. Generate each with:
|
||||
# node -e "console.log(require('crypto').randomBytes(48).toString('base64url'))"
|
||||
JWT_SECRET=<random value 1>
|
||||
JWT_CUSTOMER_SECRET=<random value 2, NOT the same as above>
|
||||
JWT_SECRET=<random value 1, 32+ characters>
|
||||
JWT_CUSTOMER_SECRET=<random value 2, 32+ characters, NOT the same as above>
|
||||
# Every origin the panel and the two apps are opened from, comma-separated.
|
||||
CORS_ORIGINS=https://adm.example.ir,https://app.example.ir,https://app2.example.ir
|
||||
```
|
||||
|
||||
The API **refuses to start** in production until all three are right, and
|
||||
prints every problem at once (REL-01, REL-03, SEC-04 —
|
||||
docs/SERVER-SETUP-CHECKLIST.md). A random value:
|
||||
`node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`.
|
||||
|
||||
Inside Docker the database host is `db`, not `localhost`.
|
||||
|
||||
Everything else is optional and may be left blank. Two you may want now:
|
||||
@@ -36,8 +43,10 @@ Everything else is optional and may be left blank. Two you may want now:
|
||||
```dotenv
|
||||
# Delivery only — a fixed OTP that signs in ANY active customer.
|
||||
# Exactly 4 digits, or it cannot be typed into the apps. REMOVE before the
|
||||
# real release (see "Before the real release" below).
|
||||
# real release (see "Before the real release" below). The production image
|
||||
# refuses to start with it unless the server is marked as a test server:
|
||||
OTP_MASTER_CODE=1379
|
||||
TEST_SERVER_ALLOW_MASTER_CODE=yes
|
||||
|
||||
# SMS goes through the "relay" gateway with nothing to set (D26).
|
||||
# SMS_PROVIDER=none turns sending off; the server still starts either way.
|
||||
@@ -112,8 +121,8 @@ before it is a production release. The first three are blocking.
|
||||
| # | Do this | Why it matters |
|
||||
|---|---|---|
|
||||
| **1** | **Set `JWT_CUSTOMER_SECRET` to its own random value**, different from `JWT_SECRET`. | Not a cosmetic default. Unset, the API signs customer and sales-expert **app** tokens with `JWT_SECRET` — *the same key admin tokens use* — and the two token realms stop being cryptographically separate. The only remaining barrier is the `typ` check in `jwt.strategy.ts`, and a sales expert's app token carries their **AdminUser id** in `sub`. Two independent defences collapse into one. A loud boot banner fires while it is missing. **REL-03 (Critical).** |
|
||||
| **2** | **Remove `OTP_MASTER_CODE` from the environment.** | While set, **any active customer signs in with that one fixed code**. Unset or empty disables it completely; the API prints a banner at boot for as long as it is armed. **REL-01 (Critical).** |
|
||||
| **3** | **Set real `JWT_SECRET`.** | Without it the API falls back to a well-known development string, so anyone can forge an admin token. |
|
||||
| **2** | **Remove `OTP_MASTER_CODE` and `TEST_SERVER_ALLOW_MASTER_CODE` from the environment.** The API now refuses to start with the code set unless that flag says this is a test server. | While set, **any active customer signs in with that one fixed code**. Unset or empty disables it completely; the API prints a banner at boot for as long as it is armed. **REL-01 (Critical).** |
|
||||
| **3** | **Set real `JWT_SECRET` and `CORS_ORIGINS`.** The API refuses to start without them. | Without the key anyone could forge an admin token; without the origin list any website could call the API from a visitor's browser. |
|
||||
| **4** | **Confirm SMS is going out.** The default `relay` gateway needs no credentials (D26); check `docker compose logs api` for `SMS provider: relay` and send yourself a login code. | If the gateway refuses the `host` value (inside Docker it is the container's IP), set `SMS_RELAY_HOST`. A failed send is only a log line — the server still boots (D20). **REL-02.** |
|
||||
|
||||
| **5** | **Accounting integration (D27), only if it will be switched on:** set `ACCOUNTING_SECRET_KEY`; count products with no image (they disappear from the app — query in ISSUES ACC-05) and photograph them first; for Zarrin, run the trial on temporary documents first (ACC-01). | The integration itself is off until an admin turns it on in «اتصال حسابداری»; nothing changes before that except the no-image rule, which applies to every product. |
|
||||
|
||||
Reference in New Issue
Block a user