docs(delivery): add a step-by-step quick start and a full pre-release section
1. "0. Quick start — run these in order" at the top of DELIVERY.md: five steps
from an empty machine to a verified running stack, copy-pasteable, with no
decisions to make. Covers which env vars must have a value (and how to
generate the two JWT secrets), `pnpm build:release` BEFORE
`docker compose build`, what the boot log should say, which banner is
expected and which means a step was missed, and how to run the end-to-end
check. Every command in it was executed verbatim to confirm it works.
2. "Before the real release" replaces the old three-item list:
- A. blocking config: real JWT_SECRET and a SEPARATE JWT_CUSTOMER_SECRET,
remove OTP_MASTER_CODE, configure the SMS credentials
- B. credentials the reference leaked and that must be rotated (SEC-12,
SEC-06) — same live accounts, never copied into this repo
- C. parity work not finished: admin (financial documents, users and
permissions incl. the still-open PERM-01, reports) and apps (MON-08 first,
then STUB-05 SMS recipients, STUB-04, STUB-03, analytics), pointing at
ISSUES.md for the rest
Also removed the obsolete `version:` key from docker-compose.yml, which printed
a deprecation warning on every compose command and made the checklist's output
noisy. Config re-validated.
Verified after these changes: container bundle is byte-identical to the local
release build (sha256 match), and that container was re-tested end to end:
compose e2e 32/32, complete-profile 38/38, report 40/40, expert login 36/36.
Locally: jest 54/54, build 5/5, typecheck clean on all four apps.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
24b7a4c395
commit
2de90c6b35
+151
-18
@@ -8,25 +8,158 @@ Both merged with **no conflicts** (`phase-1-admin` already contained
|
||||
|
||||
---
|
||||
|
||||
## ⚠ Before the real release — do these three things
|
||||
## 0. Quick start — run these in order
|
||||
|
||||
1. **Set `JWT_CUSTOMER_SECRET` to its own random value.** This one is a real
|
||||
security boundary, not a default. If it is unset, the API signs customer and
|
||||
sales-expert **app** tokens with `JWT_SECRET` — *the same key admin tokens
|
||||
use* — so the two token realms stop being cryptographically separate. The
|
||||
only thing still keeping an app token off the admin routes is the `typ`
|
||||
check in `jwt.strategy.ts`, and a sales expert's app token carries their
|
||||
**AdminUser id** in `sub`. Two independent defences become one. The API
|
||||
prints a loud banner at boot while it is missing. Tracked as **REL-03
|
||||
(Critical)**.
|
||||
2. **Remove `OTP_MASTER_CODE` from the environment.** While it is set, **any
|
||||
active customer can sign in with that one fixed code**. Unset or empty
|
||||
disables it completely, and the API prints a warning banner at boot for as
|
||||
long as it is armed. Tracked as **REL-01 (Critical)** in
|
||||
`docs/parity/ISSUES.md`.
|
||||
3. **Configure the SMS credentials** (`SMS_TREZ_*`). Until then no customer can
|
||||
receive a real login code — messages are written to the log instead of sent.
|
||||
Tracked as **REL-02**.
|
||||
Copy-paste, top to bottom. Nothing here needs a decision.
|
||||
|
||||
### Step 1 — fill in the environment
|
||||
|
||||
Create `.env` in the repo root. These **must** have a value:
|
||||
|
||||
```dotenv
|
||||
# Database
|
||||
POSTGRES_USER=postgres
|
||||
POSTGRES_PASSWORD=<choose a strong password>
|
||||
POSTGRES_DB=goldbank
|
||||
DATABASE_URL=postgresql://postgres:<the same password>@db:5432/goldbank?schema=public
|
||||
|
||||
# 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>
|
||||
```
|
||||
|
||||
Inside Docker the database host is `db`, not `localhost`.
|
||||
|
||||
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).
|
||||
OTP_MASTER_CODE=1379
|
||||
|
||||
# Leave blank and SMS is logged, not sent. The server still starts.
|
||||
SMS_TREZ_USERNAME=
|
||||
SMS_TREZ_PASSWORD=
|
||||
SMS_TREZ_SENDER_LINE=
|
||||
```
|
||||
|
||||
### Step 2 — build
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
pnpm build:release
|
||||
```
|
||||
|
||||
**Do not skip `pnpm build:release`, and never run `docker compose build`
|
||||
without it first.** The API image copies a bundle built here on the machine; it
|
||||
does not build inside the image. Skipping this step produces an image from
|
||||
**old code** and still reports success. This is real — it happened during this
|
||||
delivery's own test run, with a bundle four days stale. (DEPLOY-03)
|
||||
|
||||
### Step 3 — start
|
||||
|
||||
```bash
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
Database migrations and seeding run **automatically inside the API container**
|
||||
at boot. There is no manual migration step. Never run `prisma migrate reset`.
|
||||
|
||||
### Step 4 — check it came up
|
||||
|
||||
```bash
|
||||
docker compose ps # db healthy, api up
|
||||
docker compose logs api | tail -40
|
||||
```
|
||||
|
||||
In the log you should see `Nest application successfully started`, and
|
||||
`Applied migrations: …` (first run) or `No pending migrations` (later runs).
|
||||
|
||||
An `OTP_MASTER_CODE IS ACTIVE` banner is **expected** while that variable is
|
||||
set — it is a reminder, not an error, and it disappears once you remove the
|
||||
variable before the real release.
|
||||
|
||||
A `JWT_CUSTOMER_SECRET IS NOT SET` banner means **Step 1 was not done**. That
|
||||
one is a real security problem, not a warning to ignore — go back and set it.
|
||||
|
||||
### Step 5 — prove the whole flow works
|
||||
|
||||
```bash
|
||||
# Publishes the API on host port 3100 so the test can reach it.
|
||||
docker compose -f docker-compose.yml -f docker-compose.delivery-test.yml up -d api
|
||||
node apps/api/scripts/test-compose-e2e.cjs http://localhost:3100/api <OTP_MASTER_CODE>
|
||||
```
|
||||
|
||||
Expect **32 passed, 0 failed**. It signs a sales expert and an ordinary
|
||||
customer into the app, has both place an order, confirms both orders appear in
|
||||
the admin panel, and re-checks the token boundaries — all against the real
|
||||
container.
|
||||
|
||||
`docker-compose.delivery-test.yml` exists **only** for that check. The normal
|
||||
deployment uses `docker-compose.yml` alone, where the API has no host port and
|
||||
Dokploy/Traefik routes to it by domain.
|
||||
|
||||
---
|
||||
|
||||
## ⚠ Before the real release
|
||||
|
||||
Tonight's build is a **delivery build**. These are the things that must change
|
||||
before it is a production release. The first three are blocking.
|
||||
|
||||
### A. Configuration — 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. |
|
||||
| **4** | **Configure the SMS credentials** (`SMS_TREZ_USERNAME`, `SMS_TREZ_PASSWORD`, `SMS_TREZ_SENDER_LINE`). | Until then **no customer can receive a real login code** — messages are written to the log instead of sent. The server still boots (D20). **REL-02.** |
|
||||
|
||||
After changing any of these: `docker compose up -d --build` and confirm the
|
||||
`OTP_MASTER_CODE` banner is **gone** from `docker compose logs api`.
|
||||
|
||||
### B. Credentials to rotate — the reference leaked them
|
||||
|
||||
These are committed in plaintext in `henza_server` and must be assumed
|
||||
compromised by anyone who has ever had repo access. None were copied into this
|
||||
repository, but they are **the same live accounts**.
|
||||
|
||||
- **SMS panel** username + password + sender line (`app/Models/Sms.php:73-77`) — **SEC-12 (Critical)**
|
||||
- PayPing bearer token, Tahesab bearer token + IP allowlist, wallet hash pepper — **SEC-06**
|
||||
|
||||
### C. Parity work not finished
|
||||
|
||||
Phase 1 stopped after the Persons section when delivery mode began (D19). The
|
||||
**Phase 1 gate still stands** as the next milestone.
|
||||
|
||||
**Admin — not yet audited field-by-field.** Each has had only the breakage scan
|
||||
(`scan-admin-b2b.cjs`, 34 endpoints, 0 broken), not a parity audit:
|
||||
|
||||
- **Financial documents** (اسناد) — the money path; highest priority of the three
|
||||
- **Users & permissions** (کاربران و دسترسیها) — includes **PERM-01**, still
|
||||
open: the database holds **178** distinct permission ids against the UI's
|
||||
**174**. Unresolved because fixing it means **deleting rows**, which needs
|
||||
your approval.
|
||||
- **Reports** (گزارشها)
|
||||
- Also untouched: products/categories/variants, SMS templates, promotions,
|
||||
cities, branches, workshops.
|
||||
|
||||
**Apps — known gaps.**
|
||||
|
||||
| ID | What |
|
||||
|---|---|
|
||||
| **MON-08** | `wagePercent`, `discountPercent` and `flatRialCharge` are taken from the **client** on order creation, so a caller can set their own making charge. `rashWeightGrams` is correctly read from the variant. The reference is *worse* (it trusts client `rash_weight` too), so this is already a partial improvement — but it is still a money path trusting client input. **Fix this first in Phase 2.** |
|
||||
| **STUB-05** | Admin SMS recipients are hardcoded mobile numbers scattered across six places in the reference. Port as `SMS_ADMIN_RECIPIENTS` (comma-separated), the way `PROFILE_ADMIN_MOBILES` already works — not by copying the literals. Staff changes should not need a deploy. |
|
||||
| **STUB-04** | Live gold price is **pull-on-mount only** — no socket, no polling. `quotedAt` renders as a Jalali **date**, so a stale rate looks current. |
|
||||
| **STUB-03** | All list filtering and paging is **client-side** over one flat `GET /catalog/products`; no server `page`/`limit`. |
|
||||
| **Analytics** | The reference PWA calls `useCustomAnalytics` on every route change (`henza_pwa/src/router/index.ts:249`); neither target app has any analytics. Deliberate — it needs a provider decision from you, not a port. |
|
||||
|
||||
**`docs/parity/ISSUES.md`** holds the full list with evidence and file
|
||||
references — currently **40+ entries**, including everything above plus the
|
||||
reference bugs deliberately not ported (SEC-14, SEC-16, APP-03, and the two
|
||||
D21 items). Phase 3 is the pass over that file.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
version: '3.8'
|
||||
|
||||
# No `version:` key — it is obsolete in Compose v2 and only prints a warning on
|
||||
# every command, which made the delivery checklist's output noisy.
|
||||
services:
|
||||
db:
|
||||
image: docker.arvancloud.ir/postgres:15-alpine
|
||||
|
||||
Reference in New Issue
Block a user