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>
45 lines
1.6 KiB
TypeScript
45 lines
1.6 KiB
TypeScript
import { Body, Controller, Get, Param, Patch, Post } from '@nestjs/common';
|
|
import { OrdersService } from './orders.service';
|
|
import { CreateOrderDto } from './dto/create-order.dto';
|
|
import { UpdateOrderStatusDto } from './dto/update-order-status.dto';
|
|
import { Public, RequirePermission } from '../auth/guards';
|
|
|
|
/**
|
|
* Customer-facing order routes.
|
|
*
|
|
* POST / and GET /:id stay `@Public()` for now: the target has no customer
|
|
* session yet, so the customer id travels in the request body. The reference
|
|
* derived it from the `api-customer` guard instead
|
|
* (henza_server ApiMobileController.php:533 reads `auth()->id()`), which is what
|
|
* stage 2 of the security hotfix restores. Tracked as SEC-02 / B1-B2 in
|
|
* docs/parity/ISSUES.md.
|
|
*
|
|
* PATCH /:id/status was public too, which was worse in kind: it let an
|
|
* anonymous caller move any order's status AND attribute the change to any
|
|
* admin via the DTO's actorAdminUserId. It is an admin operation in the
|
|
* reference, it had no caller in this repo (the admin panel posts to
|
|
* admin/orders/status instead), so it is now behind the order-status rid.
|
|
*/
|
|
@Controller('orders')
|
|
export class OrdersController {
|
|
constructor(private readonly orders: OrdersService) {}
|
|
|
|
@Public()
|
|
@Post()
|
|
create(@Body() dto: CreateOrderDto) {
|
|
return this.orders.createOrder(dto);
|
|
}
|
|
|
|
@Public()
|
|
@Get(':id')
|
|
get(@Param('id') id: string) {
|
|
return this.orders.getById(id);
|
|
}
|
|
|
|
@Patch(':id/status')
|
|
@RequirePermission('order.list.sts.submit')
|
|
updateStatus(@Param('id') id: string, @Body() dto: UpdateOrderStatusDto) {
|
|
return this.orders.updateStatus(id, dto.status, dto.actorAdminUserId, dto.note);
|
|
}
|
|
}
|