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>
10 KiB
title, tags, aliases
| title | tags | aliases | |||||
|---|---|---|---|---|---|---|---|
| User |
|
|
User
Last updated: 2026-05-29 — aligned with code (see Doc vs Code Audit Report)
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 definitionbackend/src/models/User.ts:257— model export
[!note] Email change re-verification When a profile update (
PUT /api/user/profile,userController.updateUserProfile) changesisEmailVerified = false, generates a 6-digitemailVerificationCode(valid 15 minutes), stores it onemailVerificationCode/emailVerificationCodeExpires, and emails the code to the new address. The user must then confirm viaPOST /api/user/profile/email/verify(or request a new code withPOST /api/user/profile/email/resend-verification).
[!note] Wallet ownership proof
PATCH /api/user/wallet-addressaccepts 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 setsprofile.walletProofVerified = trueandprofile.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
roleandstatusindexes. ThereferralCode,referredBy, andpoints.levelindexes documented below are not yet present inUser.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 (userIdas string), RequestTemplate (sellerId), Dispute (buyerId,sellerId,adminId), BlogPost (author.id), Address (userId), Review (sellerId,reviewerId), PointTransaction (user,referredUser), ShopSettings (sellerId).
State Transitions
stateDiagram-v2
[*] --> active : signup verified
active --> suspended : admin action
suspended --> active : admin restore
active --> deleted : self-delete
suspended --> deleted : admin purge
deleted --> [*]
Common Queries
// 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.