From ebca66dd4c768fd2e8a559aa51e1ad8308635ec7 Mon Sep 17 00:00:00 2001 From: sdarbinyan Date: Thu, 13 Aug 2026 07:28:47 +0400 Subject: [PATCH] docs: backend TODOs for the four Phase 0 items needing backend work MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds §12 to the living backend reference: admin role claim, HttpOnly session cookie, server-side order pricing, and a real order audit trail, each with the proposed API/JSON shape. Co-Authored-By: Claude Sonnet 5 --- BACKEND-API-REFERENCE.md | 58 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/BACKEND-API-REFERENCE.md b/BACKEND-API-REFERENCE.md index 0dcb822..d2df1b5 100644 --- a/BACKEND-API-REFERENCE.md +++ b/BACKEND-API-REFERENCE.md @@ -454,3 +454,61 @@ Per-domain migration pattern for the six no-seam admin domains (Orders, Products - **`ADMIN_DASHBOARD_METRICS_GATEWAY` and `USER_EXPERIENCE_REPOSITORY` token factories return the mock/local class in every mode** — a real implementation must be written *and* explicitly bound; the seam existing does not mean a real backend is one line away. For open product/business decisions this document deliberately does not resolve (rate limiting posture, refresh-token reuse detection, tenant-scoped auth, API versioning scheme, etc.), see [GAPS-AND-IMPROVEMENTS.md](GAPS-AND-IMPROVEMENTS.md). + +--- + +## 12. Frontend-blocked TODOs — needs backend + +Raised during the Phase 0 security hardening pass (see the sprint plan). Each of these has a client-side mitigation already in place where one exists, but none of them close the actual gap without a backend change. + +### 12.1 Admin role claim on the session + +**Gap:** `adminAuthGuard` (Mechanism A, Telegram/QR) only checks "is there an active session" — the session API has no concept of admin role at all, so the frontend cannot enforce permissions server-authoritatively. Client mitigation: `AdminPermissionsService` derives a cosmetic permission set by matching the Telegram username against the mock Users domain locally — this is UI-only and trivially bypassed by calling the API directly. + +**Ask:** either (a) add a `role` field to the existing `GET /users/sessions/{id}` response when the session belongs to a registered admin, or (b) finish Mechanism B (Ed25519 challenge/response, already wired client-side, `/challenge` and `/verify` currently 404) so the JWT `role` claim becomes real. Whichever is chosen, every admin-mutating endpoint must independently authorize the request — a role claim on the session is necessary but not sufficient. + +Proposed minimal shape for option (a), added to the existing poll response (§2a): +```json +{ + "webSessionID": "3f1c2a0e-4e21-4d3a-9e77-1e8f6a2d9c11", + "status": "active", + "user": { "id": 8823771, "username": "buyer_ivan", "firstName": "Ivan", "lastName": "P" }, + "expiresAt": "2026-07-26T05:00:00Z", + "adminRole": "admin" +} +``` +`adminRole` absent/null → treat as non-admin regardless of what `/backoffice/**` UI is reachable client-side. + +### 12.2 HttpOnly session cookie + +**Gap:** the customer session cookie (`webSessionID`, `services/auth.service.ts`) is set via `document.cookie` from the frontend, which means it cannot be `HttpOnly` — only a `Set-Cookie` response header from the backend can set that flag, and JS-set cookies are readable by any injected script. Client mitigation: CSP hardened on all three nginx tenant blocks (was missing entirely on two of three) as defense-in-depth, but this does not close the gap. + +**Ask:** `POST /users/sessions` and `GET /users/sessions/{id}` issue the session id via `Set-Cookie: webSessionID=…; HttpOnly; Secure; SameSite=Lax; Max-Age=…` instead of (or in addition to, during migration) returning it in the JSON body. Once that ships, the frontend stops writing `document.cookie` itself and relies on the browser sending the cookie automatically; `credentials: 'include'` needs enabling on the relevant HTTP calls. + +### 12.3 Server-side order pricing + +**Gap:** `POST` order creation (§7) let the client send a computed, discount-applied `price` per line item with no server-side revalidation. Client fix already shipped: `CreateOrderRequest.items` no longer sends `price` — only `{ productId, name, quantity }`. + +**Ask:** the order-creation endpoint must price every line item itself by looking up `productId` in its own catalog (applying whatever discount/promo logic is authoritative server-side), and reject/[400] if the resulting total doesn't reconcile with what the client displayed (or just recompute and use the server total as-of-record, ignoring any client total entirely). Example of the request shape now sent: +```json +{ + "items": [{ "productId": "prod_1042", "name": "Sample Product", "quantity": 2 }], + "customer": { "name": "Ivan P", "email": "ivan@example.com", "phone": "79991234567" }, + "payment": { "method": "card", "currency": "RUB" } +} +``` +Separately, `createCartPayment()` (payment-gateway charge creation) still sends a client-computed `amount` — that field can't simply be dropped, since it's what tells the payment provider how much to charge. That endpoint must independently revalidate `amount` against its own pricing before creating the charge, and reject on mismatch. + +### 12.4 Real order audit trail + +**Gap:** `AdminOrder` had no actor/audit field at all. Client fix already shipped: `AdminOrderTimelineEntry.actor` now exists and is populated from the signed-in admin's display name in the local mock gateway — but that's client-only bookkeeping with no server-side record. + +**Ask:** when admin Orders CRUD gets a real backend (§10, step 6), every mutating endpoint (`updateStatus`, `requestRefund`, `addNote`, etc.) should record who performed the action server-side (from the authenticated session/JWT, not a client-supplied field) and return it in the order/timeline response: +```json +{ + "timeline": [ + { "status": "processing", "timestamp": "2026-08-13T10:15:00Z", "eventKey": "statusChanged", "actor": "anna@dexar.market" } + ] +} +``` +`actor` must be derived server-side from the authenticated caller, never trusted from the request body.