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
@@ -320,14 +320,21 @@ export class AdminOrdersService {
|
||||
}),
|
||||
);
|
||||
|
||||
const order = await this.orders.createOrder({
|
||||
customerId: payload.customer_id,
|
||||
items,
|
||||
// The admin builder serves the omde (major/*) flow, so orders it creates
|
||||
// belong to the wholesale channel — legacy wrote them to the `orders`
|
||||
// table, which MajorOrderController::index listed.
|
||||
channel: OrderChannel.WHOLESALE,
|
||||
});
|
||||
// The customer id is now a separate argument rather than a body field, so
|
||||
// the customer-facing route can take it from the caller's token (SEC-02).
|
||||
// This path is different: an operator legitimately places an order FOR a
|
||||
// customer, so it passes the one the builder selected. The route itself is
|
||||
// admin-guarded, which is what makes that safe.
|
||||
const order = await this.orders.createOrder(
|
||||
{
|
||||
items,
|
||||
// The admin builder serves the omde (major/*) flow, so orders it creates
|
||||
// belong to the wholesale channel — legacy wrote them to the `orders`
|
||||
// table, which MajorOrderController::index listed.
|
||||
channel: OrderChannel.WHOLESALE,
|
||||
},
|
||||
payload.customer_id,
|
||||
);
|
||||
|
||||
// legacy `sts`: advance the new order to the chosen delivery status.
|
||||
const target = this.statusFromLegacyCode(payload.sts);
|
||||
|
||||
Reference in New Issue
Block a user