Files
tehran_taj/docs/parity/CHANGELOG.md
T

95 lines
4.2 KiB
Markdown
Raw Normal View History

# CHANGELOG — parity changes
Every change made to `gold-banking-monorepo` during the parity project, with the
reason and the reference path that justifies it.
Format:
```
## <date> — <phase> — <group title>
- **What:** <the change>
- **Why:** <the parity gap it closes>
- **Reference:** <henza_* path:line>
- **Target:** <gold-banking-monorepo path:line>
- **Matrix row:** <id in 01-ADMIN-MATRIX.md / 02-APPS-MATRIX.md>
- **Commit:** <sha>
```
---
## 2026-09-21 — Phase 0 — Setup
- **What:** Created branch `parity/phase-0` and `docs/parity/` scaffolding
(`PROGRESS.md`, `ISSUES.md`, `CHANGELOG.md`).
- **Why:** Persistent state required by `CLAUDE.md` §6.
- **Reference:** n/a
- **Target:** `docs/parity/`
- **Matrix row:** n/a
_No source code changed in Phase 0 (read-only phase)._
---
## 2026-09-22 — Security hotfix, stage 1 — close the unauthenticated write paths
Branch `parity/security-hotfix`. Authorised by the user's rulings D7, T1–T3.
### 1. Ledger routes are admin-only (SEC-09)
- **What:** removed the controller-level `@Public()` from `AccountingController`
and gave each route a rid: `wallet.list` (balance), `wallet.charge`
(reconcile), `document.create` (both repayments), `document.list.confirm`
(confirm entry).
- **Why:** four of the five routes WRITE to the ledger and were reachable with no
token at all. Faithful to the reference, whose entire admin route file is
unauthenticated, but contrary to confirmed decision D1.
- **Reference:** `henza_server/routes/admin.php:12,272` (the `auth:api` wrapper is
commented out at both ends)
- **Target:** `apps/api/src/modules/accounting/accounting.controller.ts`
- **Breakage check:** `api.getBalance()` is the only app-side consumer and it has
**zero call sites** in either user app (dead code, already logged as DEAD-01/02).
### 2. Order status change is admin-only (SEC-10)
- **What:** moved `@Public()` from the class to the two customer-facing handlers,
leaving `PATCH orders/:id/status` behind `order.list.sts.submit`.
- **Why:** it let an anonymous caller move any order's status **and** attribute
the change to any admin through the DTO's `actorAdminUserId`.
- **Reference:** an admin operation; the admin panel posts to `admin/orders/status`
(`apps/admin-panel/src/api/endpoints.ts:42,48`), so this route had no caller.
- **Target:** `apps/api/src/modules/orders/orders.controller.ts`
- **Still public:** `POST /orders` and `GET /orders/:id`, because the target has
no customer session yet — closed in stage 2.
### 3. `GET /customers` no longer leaks the customer book (SEC-08)
- **What:** added `LOGIN_LOOKUP_SELECT` (`id`, `mobile`, `isActive`,
`isArchived`) and pointed `list()` at it. Removed `fullName`, `nationalCode`,
`goldDebtGrams`, `fiatDebtRial`, `goldCreditLimitGrams`, `verificationStep`,
`cityId`, `referredByAgentId`, `nationalIdImageUrl`, `businessLicenseUrl`,
`shopImageUrl`, `createdAt`, `updatedAt` from that response.
- **Why:** the route is unauthenticated because both apps match the phone number
in the browser, so it shipped every customer's national ID number, debts,
credit limit and identity-document URLs to any visitor.
- **Reference:** login was `POST userAuth` → SMS → `POST userVerfiy`, returning
exactly one customer after the code matched
(`henza_server/app/Http/Controllers/Api/ApiMobileController.php:39-78`).
- **Target:** `apps/api/src/modules/customers/customers.service.ts`,
plus `CustomerSummary` narrowed in **both** apps' `src/api/client.ts`
(parity rule: the same logic change in both).
- **Route survives** until stage 2 replaces the client-side phone match with OTP.
### 4. Test script
- **What:** added `apps/api/scripts/auth-smoke.sh` — asserts 401 without a token,
403 with a low-privilege token, 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.
- **Result:** **23 passed, 0 failed** against a live server and the dev seed.
### Not changed
`/uploads` — per ruling T2, uploaded files including identity documents stay
public. The filename check requested alongside it passed with no changes needed
(md5 of time + name + UUID; no directory listing).