docs: complete code-reality alignment for remaining docs + reconcile issue set
Remaining docs updated to match code (the docs that the first pass had not covered):
- Flows: Chat, Referral, Rating, Registration, Google OAuth, Negotiation, Payout,
Trezor Safekeeping — corrected endpoints, socket events, status enums, auth gaps
- API Reference: User API, Trezor API — admin route prefix/verb/status corrections,
added undocumented endpoints (ton-proof challenge, profile email verify,
GET /trezor/account, POST /trezor/verify-operation)
- Data Models: Chat, Notification, Payment, PointTransaction, User — corrected
enums (PaymentProvider, escrowState, PointTransaction.type, User.status),
90-day notification TTL, soft-delete semantics, wallet fields
Trezor "zero frontend" finding (audit C31/C32) corrected as STALE:
- Verified current code HAS a full frontend Trezor implementation (admin/trezor
page, TrezorSettingsView, trezorConnector via @trezor/connect-web,
TrezorSignDialog, actions/trezor.ts building the {message,signature} object)
- Fixed Trezor Safekeeping Flow doc (removed false "no frontend" warnings)
- Reclassified ISSUE-012 as invalid/superseded with explanation
Issue set reconciled to a single canonical numbering (ISSUE-001..054):
- Adopted the comprehensive 51-issue set (long-slug, fully indexed)
- Removed 35 superseded short-slug duplicates from the first pass
- Removed a duplicate ISSUE-046 file
- Added 3 issues the 51-set lacked: ISSUE-052 (completed-not-counted-in-stats),
ISSUE-053 (axios 401-only interceptor), ISSUE-054 (rate limiter counts all attempts)
- Regenerated Issues Index: 53 open (14 critical, 39 major) + 1 invalid
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,8 @@ tags: [api, user, reference]
|
||||
|
||||
# User API
|
||||
|
||||
> **Last updated:** 2026-05-29 — aligned with code (see [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md))
|
||||
|
||||
Two routers are mounted for users:
|
||||
|
||||
- `/api/user/*` - the new controller pattern in [`backend/src/services/user/userControllerRoutes.ts`](../../backend/src/services/user/userControllerRoutes.ts) wired to `userController`.
|
||||
@@ -75,29 +77,78 @@ Avatar upload is handled by the [[File API]]:
|
||||
|
||||
### GET /api/user/wallet-address
|
||||
|
||||
**Description:** Returns the caller's stored EVM wallet address (or `null`).
|
||||
**Description:** Returns the caller's stored wallet address plus its chain type and provider (each `null` if unset).
|
||||
**Auth required:** Bearer JWT
|
||||
**Response 200:** `{ "success": true, "data": { "walletAddress": "0x..." | null } }`
|
||||
**Response 200:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"walletAddress": "0x..." , // or null
|
||||
"walletType": "evm" , // "evm" | "ton" | null (the chain family)
|
||||
"walletProvider": "evm" // e.g. "evm" | "telegram-wallet" | null
|
||||
}
|
||||
}
|
||||
```
|
||||
(Earlier docs listed only `walletAddress`; the endpoint also returns `walletType` and `walletProvider`.)
|
||||
|
||||
### PATCH /api/user/wallet-address
|
||||
|
||||
**Description:** Verifies an EIP-191 signed message and stores `profile.walletAddress`. The server uses `ethers.verifyMessage(message, signature)` and rejects if the recovered address does not match.
|
||||
**Description:** Stores a verified wallet address. Supports **both EVM and TON**:
|
||||
- **EVM** (`walletType` omitted or not `'ton'`): the address must pass `ethers.isAddress`, and the body must include `signature` + `message`. The server runs `ethers.verifyMessage(message, signature)` (EIP-191) and rejects if the recovered address does not match.
|
||||
- **TON** (`walletType: 'ton'`): the address is validated against a TON address regex. An optional `tonProof` payload is verified via `verifyTonProof`; if valid, `profile.walletProofVerified` is set to `true` and `profile.walletProofTimestamp` is stamped.
|
||||
|
||||
On success the server writes `profile.walletAddress`, `profile.walletType` (`'evm'` or `'ton'`), `profile.walletProvider`, and `profile.walletProofVerified`.
|
||||
**Auth required:** Bearer JWT
|
||||
**Request body:**
|
||||
```ts
|
||||
{
|
||||
walletAddress: string; // 0x-prefixed 40-hex
|
||||
signature: string; // signed `message`
|
||||
message: string; // human-readable challenge text
|
||||
walletAddress: string; // EVM 0x-address, or TON address
|
||||
walletType?: "evm" | "ton"; // defaults to "evm"
|
||||
walletProvider?: string; // defaults to "telegram-wallet" for ton, "evm" otherwise
|
||||
// EVM only:
|
||||
signature?: string; // required for EVM — signed `message`
|
||||
message?: string; // required for EVM — human-readable challenge text
|
||||
// TON only:
|
||||
tonProof?: TonProofPayload; // optional; when valid sets walletProofVerified=true
|
||||
}
|
||||
```
|
||||
**Response 200:** `{ "success": true, "data": { "walletAddress": "0x..." } }`
|
||||
**Response 200:** `{ "success": true, "data": { "user": { /* sanitized user */ }, "walletProofVerified": boolean } }`
|
||||
**Errors:**
|
||||
- `400` missing fields, malformed address, signature mismatch
|
||||
- `400` missing/invalid fields, malformed address, EVM signature mismatch, invalid TON proof
|
||||
- `404` user not found
|
||||
|
||||
The legacy alias `PATCH /api/users/wallet-address` performs the same logic.
|
||||
|
||||
### POST /api/user/wallet-address/ton-proof/challenge
|
||||
|
||||
**Description:** Generates a TON proof nonce/challenge for TON wallet address verification. The returned challenge is then signed by the client and submitted for verification.
|
||||
**Auth required:** Bearer JWT
|
||||
**Response 200:** `{ "success": true, "data": { /* challenge/nonce payload */ } }`
|
||||
**Source:** Backend implements this endpoint for TON proof nonce generation.
|
||||
|
||||
## Email verification
|
||||
|
||||
### POST /api/user/profile/email/verify
|
||||
|
||||
**Description:** Re-verifies the caller's email address after an email change using a 6-digit code sent to the new address.
|
||||
**Auth required:** Bearer JWT
|
||||
**Request body:**
|
||||
```ts
|
||||
{
|
||||
code: string; // 6-digit verification code
|
||||
}
|
||||
```
|
||||
**Response 200:** `{ "success": true, "data": { /* updated user */ } }`
|
||||
**Source:** `axios.ts` defines this endpoint; used after email change flow.
|
||||
|
||||
### POST /api/user/profile/email/resend-verification
|
||||
|
||||
**Description:** Resends the 6-digit email verification code to the caller's (new) email address.
|
||||
**Auth required:** Bearer JWT
|
||||
**Response 200:** `{ "success": true }`
|
||||
**Source:** `axios.ts` defines this endpoint; used in email change / re-verification flow.
|
||||
|
||||
## Contacts and search
|
||||
|
||||
### GET /api/users/contacts
|
||||
@@ -122,7 +173,17 @@ The legacy alias `PATCH /api/users/wallet-address` performs the same logic.
|
||||
|
||||
## Admin: user management
|
||||
|
||||
These are duplicated across the two routers. The newer controller variants live under `/api/user/admin/*`; the legacy bodies live under `/api/users/admin/*`. All require `req.user.role === 'admin'` (the legacy routes check inline; the controller routes only check `authenticateToken` and the controller enforces the role).
|
||||
> **Note on the two admin route groups (prefix inconsistency).** There are TWO parallel admin route groups:
|
||||
> - **Singular `/api/user/admin/*`** — the NEW controller (`userControllerRoutes.ts` → `userController`). This is where create / delete / status / role / list / dependencies are actually *registered* on the new controller.
|
||||
> - **Plural `/api/users/admin/*`** — the LEGACY router (`userRoutes.ts`), which also mounts admin sub-routes (status, role, password, single-user fetch/update, resend-verification, stats).
|
||||
>
|
||||
> ⚠️ **The frontend consistently calls the PLURAL `/api/users/admin/*`** (see `frontend/src/lib/axios.ts`, all paths under `endpoints.users.admin.*`). So the singular create/delete/status/role/list paths below are *documented*, but in practice the frontend hits the legacy plural group. Both are listed; treat the plural group as the frontend-effective reality.
|
||||
>
|
||||
> ⚠️ **Note on HTTP verbs (KNOWN BUG):** The frontend `updateUserStatus` and `updateUserRole` calls (`frontend/src/actions/user.ts`) use **`PUT`** (`PUT /api/users/admin/:id/status`, `PUT /api/users/admin/:id/role`). The backend registers these as **`PATCH`** only (both the legacy and new routers). The verbs do not match — treat `PATCH` as the authoritative backend verb; the `PUT` calls will not route.
|
||||
>
|
||||
> ⚠️ **Note on status values (KNOWN BUG):** The frontend `updateUserStatus` TypeScript type is `'active' | 'inactive' | 'pending'`. The backend `User.status` enum is `'active' | 'suspended' | 'deleted'`. So:
|
||||
> - `'inactive'` and `'pending'` are **rejected/ignored** by the backend (the new controller only applies `status` when it is one of `active`/`suspended`/`deleted`).
|
||||
> - `'suspended'` — the actually-usable suspend value — is **missing from the frontend type**, so the admin UI cannot send it.
|
||||
|
||||
### POST /api/user/admin/create
|
||||
|
||||
@@ -144,31 +205,49 @@ These are duplicated across the two routers. The newer controller variants live
|
||||
**Response 201:** `{ success, data: { user } }`
|
||||
**Errors:** `400` missing fields, `403` non-admin, `409` email exists.
|
||||
|
||||
### DELETE /api/user/admin/:userId
|
||||
### DELETE /api/user/admin/:userId (new controller — SOFT delete)
|
||||
|
||||
**Description:** Hard-delete a user. Prevents self-deletion and deleting other admins.
|
||||
**Description:** **Soft-delete** — sets `status = 'deleted'` via `findByIdAndUpdate` (the user document is retained). Only blocks **self-deletion** (`userId === req.user.id`).
|
||||
**Auth required:** Bearer JWT (admin)
|
||||
**Response 200:** `{ success, data: { deletedUserId } }`
|
||||
**Errors:** `400` self-delete, `403` admin-on-admin, `404` not found.
|
||||
**Errors:** `400` self-delete, `404` not found.
|
||||
|
||||
### PATCH /api/user/admin/:userId/status
|
||||
> ⚠️ **Behavior diverges from the legacy DELETE — and a privilege concern.** The new controller’s soft-delete does **NOT** block an admin from deleting *other* admins (it only blocks deleting yourself). By contrast, the legacy `DELETE /api/users/admin/:id` (below) is a **HARD delete** (`findByIdAndDelete`, removes the document) and **does** block admin-on-admin deletion. The two endpoints behave differently in both deletion semantics (soft vs hard) and authorization (self-only vs admin-on-admin block).
|
||||
|
||||
**Description:** Activate / suspend a user.
|
||||
### DELETE /api/users/admin/:id (legacy router — HARD delete)
|
||||
|
||||
**Description:** **Hard-delete** — permanently removes the user document via `findByIdAndDelete`. Blocks deleting other admins.
|
||||
**Auth required:** Bearer JWT (admin)
|
||||
**Request body:** `{ isActive: boolean; reason?: string }`
|
||||
**Response 200:** `{ success, data: { user: { _id, isActive, statusUpdatedAt } } }`
|
||||
**Errors:** `403` admin-on-admin, `404` not found.
|
||||
|
||||
### PATCH /api/user/admin/:userId/status (and legacy PATCH /api/users/admin/:id/status)
|
||||
|
||||
**Description:** Update a user's status and/or email-verified flag. Registered on the new controller as `/api/user/admin/:userId/status`; the legacy plural `/api/users/admin/:id/status` is what the frontend actually calls.
|
||||
**Auth required:** Bearer JWT (admin)
|
||||
**Request body:**
|
||||
```ts
|
||||
{
|
||||
status?: "active" | "suspended" | "deleted"; // applied only if one of these three values
|
||||
isEmailVerified?: boolean; // new controller also accepts this — sets User.isEmailVerified
|
||||
reason?: string;
|
||||
}
|
||||
```
|
||||
The new controller only writes `status` when it is exactly `active`, `suspended`, or `deleted`; any other value (e.g. the frontend's `inactive`/`pending`) is silently ignored. It additionally accepts an `isEmailVerified` boolean to flip the user's email-verified flag.
|
||||
**Response 200:** `{ success, data: { user } }` (sanitized user without password)
|
||||
**⚠️ Frontend discrepancy (KNOWN BUG):** Frontend calls this with the `PUT` verb and sends `status: 'active' | 'inactive' | 'pending'`; the backend registers `PATCH` and only honors `active`/`suspended`/`deleted`. See the admin routing note above.
|
||||
|
||||
### PATCH /api/user/admin/:userId/toggle-status
|
||||
|
||||
**Description:** Flip active/suspended without explicit body.
|
||||
**Auth required:** Bearer JWT (admin)
|
||||
|
||||
### PATCH /api/user/admin/:userId/role
|
||||
### PATCH /api/users/admin/:userId/role
|
||||
|
||||
**Description:** Change a user's role.
|
||||
**Auth required:** Bearer JWT (admin)
|
||||
**Request body:** `{ role: "buyer" | "seller" | "admin"; reason?: string }`
|
||||
**Errors:** `400` invalid role.
|
||||
**Frontend discrepancy:** Frontend calls this with `PUT` verb; backend only accepts `PATCH`.
|
||||
|
||||
### GET /api/user/admin/list
|
||||
|
||||
@@ -184,8 +263,9 @@ These are duplicated across the two routers. The newer controller variants live
|
||||
|
||||
### GET /api/users/admin/stats
|
||||
|
||||
**Description:** Aggregated user stats — total/active/verified counts, role distribution, activity buckets (24h / 7d / 30d).
|
||||
**Description:** Aggregated user stats — total/active/verified counts, role distribution, activity buckets (24h / 7d / 30d). (Undocumented previously.)
|
||||
**Auth required:** Bearer JWT (admin)
|
||||
**Note:** No frontend UI actually consumes this. The endpoint path exists in `axios.ts` (`endpoints.users.admin.stats`), but the admin overview computes its figures client-side from `getPurchaseRequests()`, not from this endpoint.
|
||||
|
||||
### GET /api/users/admin/:userId
|
||||
|
||||
@@ -210,10 +290,12 @@ These are duplicated across the two routers. The newer controller variants live
|
||||
|
||||
### POST /api/users/admin/:userId/resend-verification
|
||||
|
||||
**Description:** Regenerate the 8-digit email verification code and re-send the verification email.
|
||||
**Description:** Regenerate the email verification code and re-send the verification email.
|
||||
**Auth required:** Bearer JWT (admin)
|
||||
**Errors:** `400` user already verified.
|
||||
|
||||
> ⚠️ **Email code length inconsistency.** The legacy `userRoutes.ts` generates an **8-digit** code (`10000000 + Math.random() * 90000000`), while the new `userController` (used by `POST /api/user/profile/email/verify` and the email-change flow) generates a **6-digit** code (`crypto.randomInt(100000, 1000000)`). Code length therefore depends on which path issued it.
|
||||
|
||||
## Address book
|
||||
|
||||
Source: [`backend/src/services/address/addressRoutes.ts`](../../backend/src/services/address/addressRoutes.ts), model: [[Address]].
|
||||
|
||||
Reference in New Issue
Block a user