parity(api,apps): close SEC-02 - orders take the customer from the token
The user declined to defer this to Phase 2, correctly: a public POST /orders with customerId in the body let anyone place an order on any customer's account, and an order lands on that customer's gold debt and ledger. POST /orders is now behind CustomerAuth. OrdersService.createOrder takes the customer id as a separate argument that the controller fills from the token; the body's customerId is ignored and a mismatch is logged. The field stays optional in the DTO and in shared-types only so both apps keep validating while they still send it -- they drop it in Phase 2. The admin builder is unaffected: it legitimately places orders for a chosen customer and passes that id explicitly, behind the admin guard. GET /orders/:id is scoped to the owner. A foreign or nonexistent id both return 404, never 403, so ids stay unenumerable. Operators read through the new GET /orders/admin/:id behind rid order.list, declared before the :id route because Nest matches in declaration order. Both apps' CartView now gates on session.isLoggedIn rather than a remembered customerId, which is no longer a credential. OTP audit (requested): no master code, no bypass, no hardcoded code anywhere; one code path issues a token and only after bcrypt.compare. No SMS provider is wired up -- in development the code is logged once, guarded by NODE_ENV !== 'production'. Fixed while auditing (SEC-11): deliver() persisted the real message text, code included, into SmsMessage.body, which the admin SMS grid lists and full-text searches. Any operator with the sms rid could read live codes, defeating the hashing of codeHash. The stored row is now a redacted audit line. Also logged MON-08: wagePercent/discountPercent/flatRialCharge are still taken from the client on order creation. The reference is worse -- it takes rash_weight from the client too -- so this is already a partial improvement; logged rather than changed, per D4. Tests: auth-smoke.sh now 53 passed, 0 failed, including a spoofed body customerId being ignored in favour of the token. Typecheck clean on api, user-app, user-app-v2 and shared-types. [ref: henza_server ApiMobileController.php:533,536-545,615-632] Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
4efe543d62
commit
024e3dd045
@@ -0,0 +1,127 @@
|
||||
# Migration state — investigated before writing D0 / percent
|
||||
|
||||
Requested by the user before any new migration is written. All findings are from
|
||||
**read-only** queries; nothing was reset and `_prisma_migrations` was not touched.
|
||||
|
||||
## The five questions
|
||||
|
||||
### 1. Is `20260917120000_customer_name_parts_and_percent` recorded as applied?
|
||||
|
||||
**Yes — but as a no-op.**
|
||||
|
||||
```
|
||||
migration_name 20260917120000_customer_name_parts_and_percent
|
||||
finished_at 2026-09-18T16:03:33.956Z
|
||||
rolled_back_at null
|
||||
applied_steps_count 0 <-- zero steps ran
|
||||
```
|
||||
|
||||
`finished_at` is set and `rolled_back_at` is null, so **Prisma considers it
|
||||
applied and will never re-run it**. `applied_steps_count = 0` says no SQL
|
||||
statement was ever executed by it.
|
||||
|
||||
### 2. What is in its SQL file?
|
||||
|
||||
**Comments only — no executable statement.** The file is a placeholder that
|
||||
explains itself: it says the columns were added "directly against the live DB"
|
||||
and that it was recreated as an empty no-op so `prisma migrate status` would
|
||||
stop erroring on a missing directory.
|
||||
|
||||
### 3. Does `schema.prisma` declare `firstName` / `lastName` / `productPercent`?
|
||||
|
||||
**No.** `grep` over `prisma/schema.prisma` finds none of the three. The
|
||||
`Customer` model has `fullName` and nothing else name-related.
|
||||
|
||||
### 4. Do the columns exist in the database?
|
||||
|
||||
**No.** Querying `information_schema.columns` for `Customer` and those names
|
||||
returns only `fullName`:
|
||||
|
||||
```
|
||||
firstName absent
|
||||
lastName absent
|
||||
productPercent absent
|
||||
makingChargeOffsetPercent absent
|
||||
fullName present
|
||||
```
|
||||
|
||||
**So the placeholder file's own comment is wrong.** It claims the columns "already
|
||||
exist live"; they do not, at least not in this database (`gold_banking` on
|
||||
localhost). Whatever database that comment described, it was not this one.
|
||||
|
||||
**Consequence for the runtime:** none, and this is the good news. Prisma Client
|
||||
generates from `schema.prisma`, and the schema does not mention these fields
|
||||
either. Schema and database agree — both lack the columns. There is **no drift
|
||||
that could make Prisma Client throw at runtime**. The only thing that is wrong is
|
||||
the placeholder's comment.
|
||||
|
||||
### 5. `prisma migrate status` (read-only)
|
||||
|
||||
```
|
||||
9 migrations found in prisma/migrations
|
||||
Following migration have not yet been applied:
|
||||
20260922000000_customer_otp
|
||||
```
|
||||
|
||||
Note on that line: the OTP table **does exist** — stage 2 created it with
|
||||
`prisma db execute`, which runs SQL without writing a ledger row. So the schema
|
||||
is correct but the ledger entry is missing, and `migrate deploy` would try to
|
||||
run it again. The SQL is written with `CREATE TABLE IF NOT EXISTS`, the indexes
|
||||
with `IF NOT EXISTS`, and the FK inside a `DO $$ ... EXCEPTION WHEN
|
||||
duplicate_object` block, so a re-run is harmless. Still worth resolving cleanly —
|
||||
see the recommendation.
|
||||
|
||||
### 6. Where did the file come from?
|
||||
|
||||
```
|
||||
b80fa9b Redesign product detail page and fix icon/cart reactivity bugs
|
||||
```
|
||||
|
||||
A single commit, and the message has nothing to do with migrations — the
|
||||
placeholder was swept in as collateral to an unrelated frontend change.
|
||||
|
||||
---
|
||||
|
||||
## Recommendation
|
||||
|
||||
**The placeholder is recorded as applied, so per the user's rule it must not be
|
||||
edited.** It stays exactly as it is.
|
||||
|
||||
1. **Leave `20260917120000_...` untouched.** Editing a migration Prisma has
|
||||
recorded would make its checksum mismatch and break `migrate status` for
|
||||
everyone. Its only sin is a misleading comment, and a follow-up migration
|
||||
makes that harmless.
|
||||
2. **Write the D0 and `percent` work as a new, additive migration** with a fresh
|
||||
timestamp. It adds the columns the placeholder never added. Written to be
|
||||
idempotent (`IF NOT EXISTS`) so it is safe on a database where someone *did*
|
||||
apply the columns by hand.
|
||||
3. **Resolve the OTP ledger entry** with
|
||||
`npx prisma migrate resolve --applied 20260922000000_customer_otp`.
|
||||
That writes the ledger row without running SQL, which is exactly right here
|
||||
since the table already exists. **Needs the user's approval before running**,
|
||||
as it writes to `_prisma_migrations`.
|
||||
4. **Never** `migrate reset`, and never edit `_prisma_migrations` by hand.
|
||||
|
||||
---
|
||||
|
||||
## Naming: one field, not two
|
||||
|
||||
The user asked for a single final name. **`productPercent` is dropped;
|
||||
`makingChargeOffsetPercent` is the name.**
|
||||
|
||||
Reason: the reference has **three unrelated things** all called `percent`, and a
|
||||
vague name is how they got confused in the first place (the target currently
|
||||
reads the wrong one — CUST-02):
|
||||
|
||||
| Reference `percent` | Where | What it is |
|
||||
|---|---|---|
|
||||
| `customers.percent` | `customer_add.php:32` | a **per-customer making-charge offset**, used only to shift catalogue filter bounds (`ApiMobileController.php:391-396,76-77`) |
|
||||
| `ConfigStatic['percent']` | `AdminController.php:903,994` | the **wallet cashback rate**: `total_weight × percent/100` |
|
||||
| `order_item.percent` | `MajorOrderController.php:220` | the **per-line making charge** on a wholesale order |
|
||||
|
||||
`productPercent` would read as "a percent belonging to a product", which is the
|
||||
third meaning, not the first. `makingChargeOffsetPercent` says which of the
|
||||
three it is. It is mapped back to the legacy key `percent` in `toRow()`, so the
|
||||
admin panel's field name does not change.
|
||||
|
||||
`productPercent` is not created, so there is no duplicate field.
|
||||
Reference in New Issue
Block a user