Files
tehran_tala/docs/parity/PROGRESS.md
T
s.esmaeiliandClaude Opus 5 fdf833565f parity(api,apps): security hotfix stage 1 - close unauthenticated write paths
Authorised by rulings D7 and T1-T3 in docs/parity/DECISIONS.md. This is the
CLAUDE.md section 3 carve-out: matching the reference here would keep a money
write path open to anonymous callers.

Closed, each behind the rid the legacy vocabulary already had:
  GET   accounting/customers/:id/balance    -> wallet.list
  POST  accounting/customers/:id/reconcile  -> wallet.charge
  POST  accounting/repayments/gold          -> document.create
  POST  accounting/repayments/rial          -> document.create
  POST  accounting/entries/:id/confirm      -> document.list.confirm
  PATCH orders/:id/status                   -> order.list.sts.submit

Four of the accounting routes WRITE to the ledger. The order-status route was
worse in kind: its DTO takes a client-supplied actorAdminUserId, so an
anonymous caller could move any order and pin it on any admin. It had no
caller - the admin panel posts to admin/orders/status.

GET /customers kept its public status (both apps' login still matches the
phone number in the browser) but now selects only id/mobile/isActive/
isArchived. It previously returned every active customer's full name, national
code, both debts, credit limit and the URLs of their identity-document scans.
CustomerSummary narrowed in BOTH user apps in the same commit, per the
v1/v2 parity rule.

Not changed: /uploads stays public per ruling T2. The filename check requested
alongside it passed unchanged - md5(time + name + uuid), no directory listing.

Tests: apps/api/scripts/auth-smoke.sh asserts 401 without a token, 403 with a
low-privilege token (ut 88, 5 unrelated rids) and pass-through for a super
admin on every closed route, plus that the public routes still answer 200 and
that GET /customers exposes only the four login fields. 23 passed, 0 failed
against a live server. Typecheck clean on api, user-app and user-app-v2.

[ref: henza_server/routes/admin.php:12,272; ApiMobileController.php:39-78]

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 00:43:11 +03:30

121 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PROGRESS — Reference Parity Project
> Read this file FIRST at the start of every session and after every `/compact`.
> Rules live in `CLAUDE.md` (workspace root). Method lives in the `reference-parity-audit` skill.
## Current state
| Field | Value |
|---|---|
| **Phase** | Security hotfix (out-of-band, authorised by D7/T1–T3) — **stage 1 ✅ done**, stage 2 next |
| **Branch** | `parity/security-hotfix` |
| **Status** | Stage 1 complete and tested live: 23/23 smoke assertions pass. Decisions D0–D12 + T1–T3 recorded. |
| **Next step** | Stage 2 — customer JWT (`typ` claim, separate secret) + real OTP + migrate both apps' login; then remove `GET /customers` from the public surface |
| **Blocked on** | nothing for stage 2. Still pending later: D0 migration + `percent` migration must be **shown and approved before running** |
| **Last updated** | 2026-09-22 |
### Security hotfix — stage 1 result
| Route | Was | Now | Rid |
|---|---|---|---|
| `GET accounting/customers/:id/balance` | public | admin | `wallet.list` |
| `POST accounting/customers/:id/reconcile` | public **write** | admin | `wallet.charge` |
| `POST accounting/repayments/gold` | public **write** | admin | `document.create` |
| `POST accounting/repayments/rial` | public **write** | admin | `document.create` |
| `POST accounting/entries/:id/confirm` | public **write** | admin | `document.list.confirm` |
| `PATCH orders/:id/status` | public **write** | admin | `order.list.sts.submit` |
| `GET customers` | 18 fields incl. national ID, debts, doc URLs | 4 fields | still public until stage 2 |
Test: `apps/api/scripts/auth-smoke.sh` — **23 passed, 0 failed**.
Typecheck: api ✅ · user-app ✅ · user-app-v2 ✅.
## ⚠️ Read `DECISIONS.md` before doing anything
All user rulings live there (D0–D12). Highlights that change how work is done:
- **D12 — the matrix is FIELD-BY-FIELD, not page-by-page**, and a 4-step
round-trip test per entity is mandatory. Both rules were added to the
`reference-parity-audit` skill (Step C rule C0, Step E rule E0).
- **D0** — split `fullName` into `firstName`/`lastName` via an additive migration;
**show the migration and get approval before running it**.
- **D4** — never port broken behaviour, but match calculations/rounding/messages
exactly; annotate each non-replication «عمداً تکرار نشد — ISSUE #n».
- **D2** — target-only features get status ➕ and are left alone.
## Phase checklist
- [x] P0.1 — All 3 reference folders readable (`henza_server`, `henza_admin`, `henza_pwa`)
- [x] P0.2 — Branch `parity/phase-0` + `docs/parity/` created
- [x] P0.3 — Inventory all 7 projects → `_inv-*.md` (8111 lines total) → consolidated in `00-INVENTORY.md`
- [x] P0.4 — Mapping tables, missing list, extra list (`00-INVENTORY.md` §2–4)
- [x] P0.5 — Draft `PROTECTED.md`
- [x] **P0.6 — GATE passed** — `PROTECTED.md` confirmed, D0–D12 recorded
- [x] P0.7 — Urgent traces delivered: `percent`, `@Public` list, login leak
- [x] P0.8 — Skill updated with rules C0 (field-by-field) and E0 (round-trip test)
- [ ] **P1.0 — user approves: 5 ledger routes, `percent` column, D0 migration** ← HERE
- [ ] Phase 1 — Admin parity (branch `parity/phase-1-admin`, `01-ADMIN-MATRIX.md`)
- [ ] **GATE:** "Admin parity is complete and tested. Shall we move on to the apps (user-app & user-app-v2)?"
- [ ] Phase 2 — Apps parity (`02-APPS-MATRIX.md`, both user apps)
- [ ] Phase 3 — Work through `ISSUES.md` (only when user asks)
## Answered — see `DECISIONS.md` for the full text
| # | Ruling |
|---|---|
| D0 | Split `fullName` → `firstName`/`lastName`, additive migration, backfill, sync, report multi-token rows. Approval needed before running. |
| D1 | Target's stricter auth stands. Build the "reference operation ↔ rid" matrix; explain 172 vs 174. |
| D2 | Audit only what the reference has. Target-only features → ➕, untouched. |
| D3 | Build the 5 missing screens in Phase 2, both apps, plus analytics, `isLoaded`/`sts_verify_step_two` guards, guest toast+home, and v2's cart ceiling. |
| D4 | Don't port breakage; do match calculations/rounding/messages. Unit-test the price formula. |
| D5 | Leave the stray folder; delete last, own commit. |
| D6 | **OTP must verify** — parity gap, fix it (plus expiry + attempt limits, which exceed the reference). |
| D7 | `@Public` ledger routes: list delivered, awaiting approval to protect. |
| D8 | Login leak: **target**, full customer book incl. national IDs and debts. |
| D9 | `percent`: trace delivered — **the target's implementation is wrong**. |
| D10 | Sales expert: keep `AdminUser`; investigate the 5 missing fields; propose `SalesExpertProfile`. |
| D11 | `singleCustomer.vue` → diff-send. |
| D12 | **Field-by-field matrix + mandatory round-trip test.** Added to the skill. |
## Open — awaiting user approval
1. **Protect the 5 `@Public` ledger-write routes** (`DECISIONS.md` group A).
2. **`percent` schema change** — add `makingChargeOffsetPercent` to `Customer`.
3. **D0 migration file** — must be shown and approved before it runs.
## Key context (survives context loss)
- **Domain correction:** the reference is a **gold-jewellery B2B/B2C e-commerce**
system, not a gold bank. No buy/sell order book, no spread, no deposit/withdraw,
no P2P transfer, no KYC levels, no daily limits (only `gram_level`), no payment
callback route, no cron, no websockets. See `00-INVENTORY.md` §0.
- **The gold price formula** (Phase 1 row #1) —
`henza_server/app/Models/ProductVariant.php:183-190`: `+extra%` → `+7%` margin →
`+9%` VAT **on the non-metal portion only** → `×grams` → `floor(/1000)*1000`.
Three superseded commented-out variants above it apply VAT to the whole price —
do **not** port those.
- **A large prior port already happened** — see `docs/ADMIN-PORT-PLAN.md` and
`docs/CHANGELOG-PORT-HISTORY.md` (80 views, 25 Nest modules, 29 Prisma models,
154 admin routes, 144/144 rids). Prior port notes **no view was ever visually
verified in a browser**.
- **Stack changed across the port:** Laravel→NestJS/Prisma, Vue2/Vuesax→Vue3/antd.
Port behaviour, not code style.
- **Reference scale is inflated:** 541 admin `.vue` files but only ~69 reachable
(rest are Vuexy template demos); 9 reference router files are dead (not imported);
~28 legacy Django-era DB tables have no migrations.
- **v1 ⇄ v2 logic gaps** (5, all v2 behind v1) are parity work, not design:
`ISSUES.md` MON-01, V2-01..05.
## Inventory files (Phase 0 working files)
| File | Scope | Lines |
|---|---|---|
| `_inv-ref-server.md` | henza_server | 1313 |
| `_inv-tgt-api.md` | apps/api | 1179 |
| `_inv-ref-admin.md` | henza_admin | 1705 |
| `_inv-tgt-admin.md` | apps/admin-panel | 1261 |
| `_inv-ref-pwa.md` | henza_pwa | 1178 |
| `_inv-tgt-userapp.md` | apps/user-app | 612 |
| `_inv-tgt-userappv2.md` | apps/user-app-v2 | 700 |
Consolidated into `00-INVENTORY.md`. Issues found: `ISSUES.md` (26 entries).