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>
154 lines
10 KiB
Markdown
154 lines
10 KiB
Markdown
---
|
|
title: User
|
|
tags: [data-model, mongoose]
|
|
aliases: [User Model, IUser, Account]
|
|
---
|
|
|
|
# User
|
|
|
|
> **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))
|
|
|
|
The core identity document for every actor in the marketplace: buyers, sellers, and admins. Stores credentials (password + WebAuthn passkeys), profile/preference data, referral bookkeeping, point balances, and a soft-delete status flag. Almost every other model carries an `ObjectId` reference back to `User`, so this collection is the relational hub of the system.
|
|
|
|
> [!note] Source
|
|
> `backend/src/models/User.ts:70` — schema definition
|
|
> `backend/src/models/User.ts:257` — model export
|
|
|
|
> [!note] Email change re-verification
|
|
> When a profile update (`PUT /api/user/profile`, `userController.updateUserProfile`) changes `email` to a new value, the controller sets `isEmailVerified = false`, generates a **6-digit** `emailVerificationCode` (valid 15 minutes), stores it on `emailVerificationCode` / `emailVerificationCodeExpires`, and emails the code to the new address. The user must then confirm via `POST /api/user/profile/email/verify` (or request a new code with `POST /api/user/profile/email/resend-verification`).
|
|
|
|
> [!note] Wallet ownership proof
|
|
> `PATCH /api/user/wallet-address` accepts both EVM and TON wallets. EVM addresses require an EIP-191 signature (`ethers.verifyMessage`); TON addresses are format-validated and may include an optional TonProof. A successful proof sets `profile.walletProofVerified = true` and `profile.walletProofTimestamp`.
|
|
|
|
## Schema
|
|
|
|
| Field | Type | Required | Default | Validation | Index | Description |
|
|
| --- | --- | --- | --- | --- | --- | --- |
|
|
| `email` | String | no | — | lowercase, trim | unique, sparse | Primary email login identifier. Nullable for Telegram-only accounts. |
|
|
| `password` | String | no | — | minlength 6 | — | Hashed password. Optional to support passkey-only, Google, and Telegram accounts. |
|
|
| `firstName` | String | no | `"کاربر"` | trim | — | Persian default ("user"). |
|
|
| `lastName` | String | no | `"جدید"` | trim | — | Persian default ("new"). |
|
|
| `role` | String | yes | `"buyer"` | enum: `admin` / `buyer` / `seller` | yes | Authorisation tier. |
|
|
| `isEmailVerified` | Boolean | no | `false` | — | — | Set to true after the email verification code is consumed. ⚠️ Changing the email via `PUT /api/user/profile` **resets this to `false`** and dispatches a fresh **6-digit** verification code to the new address (see Email verification note below). |
|
|
| `authProvider` | String | yes | `"email"` | enum: `email` / `google` / `telegram` | yes | Provider used to create the account. Existing email/password accounts remain `email`; Telegram-only users are `telegram`. |
|
|
| `telegramVerified` | Boolean | no | `false` | — | — | Set when Telegram identity has been signature-verified and linked through `TelegramLink`. |
|
|
| `emailVerificationToken` | String | no | — | — | — | Legacy token-based email verification. |
|
|
| `emailVerificationCode` | String | no | — | — | — | OTP code for email verification. |
|
|
| `emailVerificationCodeExpires` | Date | no | — | — | — | Expiry for `emailVerificationCode`. |
|
|
| `passwordResetToken` | String | no | — | — | — | Token for reset link flow. |
|
|
| `passwordResetExpires` | Date | no | — | — | — | Expiry of `passwordResetToken`. |
|
|
| `passwordResetCode` | String | no | — | — | — | OTP reset code. |
|
|
| `passwordResetCodeExpires` | Date | no | — | — | — | Expiry for OTP reset code. |
|
|
| `passkeys[]` | Subdocument array | no | `[]` | — | — | WebAuthn credentials (see below). |
|
|
| `passkeys[].id` | String | yes | — | — | — | Credential ID. |
|
|
| `passkeys[].publicKey` | String | yes | — | — | — | Stored public key. |
|
|
| `passkeys[].counter` | Number | yes | `0` | — | — | Signature counter. |
|
|
| `passkeys[].deviceType` | String | yes | — | enum: `platform` / `cross-platform` | — | Authenticator class. |
|
|
| `passkeys[].deviceName` | String | no | — | — | — | Optional human label. |
|
|
| `passkeys[].createdAt` | Date | no | `Date.now` | — | — | Registration timestamp. |
|
|
| `profile.avatar` | String | no | — | — | — | Avatar URL. |
|
|
| `profile.photoURL` | String | no | — | — | — | Alternative photo URL. |
|
|
| `profile.phone` | String | no | — | — | — | Contact phone. |
|
|
| `profile.address.street` | String | no | — | — | — | Inline address (separate from [[Address]] book). |
|
|
| `profile.address.city` | String | no | — | — | — | — |
|
|
| `profile.address.state` | String | no | — | — | — | — |
|
|
| `profile.address.zipCode` | String | no | — | — | — | — |
|
|
| `profile.address.country` | String | no | — | — | — | — |
|
|
| `profile.bio` | String | no | — | — | — | Free-form bio. |
|
|
| `profile.website` | String | no | — | — | — | Personal website URL. |
|
|
| `profile.walletAddress` | String | no | — | — | — | On-chain wallet address (EVM `0x…` or TON). Set via `PATCH /api/user/wallet-address`. |
|
|
| `profile.walletType` | String | no | — | enum: `evm` / `ton` | — | Which chain family the stored `walletAddress` belongs to. |
|
|
| `profile.walletProvider` | String | no | — | — | — | Wallet provider label (e.g. `evm`, `telegram-wallet`). Defaults to `telegram-wallet` for TON, `evm` otherwise. |
|
|
| `profile.walletProofVerified` | Boolean | no | — | — | — | True when ownership was proven — EIP-191 signature for EVM, or a verified TonProof for TON. |
|
|
| `profile.walletProofTimestamp` | Date | no | — | — | — | When the wallet proof was last verified (only set when `walletProofVerified` is true). |
|
|
| `profile.isPublic` | Boolean | no | `false` | — | — | Whether the profile is publicly visible. |
|
|
| `preferences.language` | String | no | `"en"` | — | — | UI language. |
|
|
| `preferences.currency` | String | no | `"USD"` | — | — | Display currency. |
|
|
| `preferences.notifications.email` | Boolean | no | `true` | — | — | Opt-in for email notifications. |
|
|
| `preferences.notifications.sms` | Boolean | no | `false` | — | — | Opt-in for SMS notifications. |
|
|
| `preferences.notifications.push` | Boolean | no | `true` | — | — | Opt-in for push notifications. |
|
|
| `status` | String | no | `"active"` | enum: `active` / `suspended` / `deleted` | yes | Soft-delete and moderation flag. |
|
|
| `lastLoginAt` | Date | no | — | — | — | Updated by auth middleware. |
|
|
| `refreshTokens[]` | String[] | no | `[]` | — | — | Array of currently active JWT refresh tokens. ⚠️ Reset to `[]` on password change and on password reset, which invalidates every outstanding session and forces re-login everywhere. |
|
|
| `referralCode` | String | no | — | — | unique, sparse | **Not yet implemented** in `User.ts` — planned for referral programme. |
|
|
| `referredBy` | ObjectId → User | no | — | — | yes | **Not yet implemented** in `User.ts` — planned for referral programme. |
|
|
| `points.total` | Number | no | `0` | — | — | **Not yet implemented** in `User.ts` — planned for loyalty system. |
|
|
| `points.available` | Number | no | `0` | — | — | **Not yet implemented** in `User.ts`. |
|
|
| `points.used` | Number | no | `0` | — | — | **Not yet implemented** in `User.ts`. |
|
|
| `points.level` | Number | no | `1` | — | yes (`points.level`) | **Not yet implemented** in `User.ts` — planned for [[LevelConfig]] lookup. |
|
|
| `referralStats.totalReferrals` | Number | no | `0` | — | — | **Not yet implemented** in `User.ts`. |
|
|
| `referralStats.activeReferrals` | Number | no | `0` | — | — | **Not yet implemented** in `User.ts`. |
|
|
| `referralStats.totalEarned` | Number | no | `0` | — | — | **Not yet implemented** in `User.ts`. |
|
|
| `createdAt` | Date | auto | — | — | — | Mongoose timestamp. |
|
|
| `updatedAt` | Date | auto | — | — | — | Mongoose timestamp. |
|
|
|
|
## Virtuals
|
|
|
|
| Virtual | Returns | Definition |
|
|
| --- | --- | --- |
|
|
| `fullName` | `${firstName} ${lastName}` | `backend/src/models/User.ts:238` |
|
|
|
|
## Indexes
|
|
|
|
Defined explicitly:
|
|
|
|
- `{ email: 1 }` unique sparse — allows multiple Telegram-only users without email while preserving uniqueness for email-bearing users.
|
|
- `{ role: 1 }` — `backend/src/models/User.ts:178`
|
|
- `{ status: 1 }` — `backend/src/models/User.ts:179`
|
|
- `{ authProvider: 1 }` — supports provider-level account reporting and cleanup.
|
|
|
|
> [!warning] Missing indexes
|
|
> The schema currently defines only `role` and `status` indexes. The `referralCode`, `referredBy`, and `points.level` indexes documented below are **not yet present** in `User.ts`:
|
|
|
|
## Pre/Post Hooks
|
|
|
|
None declared at the schema level.
|
|
|
|
## Instance Methods
|
|
|
|
| Signature | Purpose |
|
|
| --- | --- |
|
|
| `toJSON(): object` | Strips `password`, `refreshTokens`, all `emailVerification*` and `passwordReset*` fields before serialisation. Defined at `backend/src/models/User.ts:243`. |
|
|
|
|
## Static Methods
|
|
|
|
None defined on the schema.
|
|
|
|
## Relationships
|
|
|
|
- **References**: [[User]] (self, via `referredBy`).
|
|
- **Referenced by**: [[PurchaseRequest]] (`buyerId`, `preferredSellerIds`, `deliveryInfo.deliveryCodeUsedBy`, `deliveryInfo.deliveryAttempts[].sellerId`), [[SellerOffer]] (`sellerId`), [[Payment]] (`buyerId`, `sellerId`), [[Chat]] (`participants[].userId`, `messages[].senderId`, `metadata.createdBy`), [[Notification]] (`userId` as string), [[RequestTemplate]] (`sellerId`), [[Dispute]] (`buyerId`, `sellerId`, `adminId`), [[BlogPost]] (`author.id`), [[Address]] (`userId`), [[Review]] (`sellerId`, `reviewerId`), [[PointTransaction]] (`user`, `referredUser`), [[ShopSettings]] (`sellerId`).
|
|
|
|
## State Transitions
|
|
|
|
```mermaid
|
|
stateDiagram-v2
|
|
[*] --> active : signup verified
|
|
active --> suspended : admin action
|
|
suspended --> active : admin restore
|
|
active --> deleted : self-delete
|
|
suspended --> deleted : admin purge
|
|
deleted --> [*]
|
|
```
|
|
|
|
## Common Queries
|
|
|
|
```ts
|
|
// Find by email (login)
|
|
User.findOne({ email: email.toLowerCase() });
|
|
|
|
// Active sellers
|
|
User.find({ role: 'seller', status: 'active' });
|
|
|
|
// Validate referral
|
|
User.findOne({ referralCode: code });
|
|
|
|
// Leaderboard by points
|
|
User.find({ status: 'active' }).sort({ 'points.total': -1 }).limit(10);
|
|
|
|
// Promote level
|
|
User.updateOne({ _id: id }, { $set: { 'points.level': newLevel } });
|
|
```
|
|
|
|
Related: [[TempVerification]], [[LevelConfig]], [[PointTransaction]], [[ShopSettings]].
|