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>
8.4 KiB
title, tags, related_models, related_apis
| title | tags | related_models | related_apis | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| Password Reset Flow |
|
|
|
Last updated: 2026-05-29 — aligned with code (see Doc vs Code Audit Report)
[!caution] Audit note — last reviewed 2026-05-29 Several discrepancies between frontend code, backend code, and this document were identified and corrected in this revision. See inline ⚠️ markers for known bugs and mismatches.
Password Reset Flow
Self-service password recovery. There are two separate reset endpoints with different security characteristics:
| Endpoint | Mechanism | Password complexity enforced? |
|---|---|---|
POST /api/auth/reset-password-with-code |
6-digit emailed code | No — no validation middleware |
POST /api/auth/reset-password |
Token-based (link in email) | Yes — passwordResetValidation requires uppercase + lowercase + digit |
The primary UI-driven path uses the code-based endpoint. The token-based endpoint is a legacy/alternative variant.
Actors
- User who has forgotten their password.
- Frontend —
frontend/src/auth/view/jwt/jwt-reset-password-view.tsx(request) andjwt-update-password-view.tsx(submit new password). - Backend —
AuthController.requestPasswordResetandAuthController.resetPasswordWithCodeinbackend/src/services/auth/authController.ts. - MongoDB —
Usercollection (passwordResetCode,passwordResetCodeExpires,refreshTokens). - Email service —
emailService.sendPasswordResetCodeEmail.
Preconditions
- The account exists and
status === "active"(deleted accounts are silently treated as non-existent). - The user has access to the email inbox associated with the account.
- A 6-digit code is valid for 1 hour (
authController.ts:556).
Step-by-step narrative
- User clicks "Forgot password?" on the sign-in page and lands at
/auth/jwt/reset-password. - User enters their email and submits.
- Frontend POSTs
POST /api/auth/request-password-reset { email }. - Backend
authController.requestPasswordReset(:542-574):User.findOne({ email, status: "active" }). If absent, returns200with the same generic message — no enumeration.- Generates a 6-digit code via
authService.generateVerificationCode()(Math.floor(100000 + Math.random() * 900000)). - Saves
passwordResetCodeandpasswordResetCodeExpires = now + 3_600_000 mson the user. - Calls
emailService.sendPasswordResetCodeEmail(email, firstName, code).
- Response:
200 "If an account with this email exists, a password reset code has been sent"regardless of outcome. - User receives the email and enters the code + new password on
/auth/jwt/update-password. - Frontend POSTs
POST /api/auth/reset-password-with-code { email, code, password }. - Backend
authController.resetPasswordWithCode(:611-657):- Validates code format
/^\d{6}$/— codes of any other length will always fail here. User.findOne({ email, passwordResetCode: code, passwordResetCodeExpires: { $gt: now }, status: "active" }). Mismatch →400 Invalid or expired reset code.- Hashes the new password with bcrypt cost 12. No password complexity validation is applied — weak passwords such as
123456oraaaaaaare accepted without error. - Sets
user.password = hashed, clearspasswordResetCodeandpasswordResetCodeExpires, wipesuser.refreshTokens = []to invalidate all existing sessions. - Saves.
- Validates code format
- Response:
200 "Password reset successfully". Frontend redirects to/auth/jwt/sign-infor a fresh login.
Sequence diagram
sequenceDiagram
autonumber
actor U as User
participant FE as Frontend
participant BE as Backend
participant DB as MongoDB
participant MAIL as Email Service
U->>FE: Click "Forgot password", enter email
FE->>BE: POST /api/auth/request-password-reset { email }
BE->>DB: User.findOne({ email, status: "active" })
alt user found
BE->>BE: code = generateVerificationCode() [6 digits]
BE->>DB: user.passwordResetCode = code\nexpires = +1h
BE->>MAIL: sendPasswordResetCodeEmail(email, firstName, code)
MAIL-->>U: Email with 6-digit code
end
BE-->>FE: 200 "if account exists, code sent"
U->>FE: Enter code + new password
FE->>BE: POST /api/auth/reset-password-with-code { email, code, password }
BE->>BE: isValidVerificationCode(code) [/^\d{6}$/]
BE->>DB: User.findOne({ email, code, expires>now })
BE->>BE: bcrypt.hash(password, 12) [no complexity check]
BE->>DB: user.password = hash\nuser.refreshTokens = []\nclear reset fields
BE-->>FE: 200 "Password reset successfully"
FE-->>U: Redirect /auth/jwt/sign-in
API calls
| Method | Endpoint | Source | Notes |
|---|---|---|---|
POST |
/api/auth/request-password-reset |
authRoutes.ts:44-47 |
Sends 6-digit code by email |
POST |
/api/auth/reset-password-with-code |
authRoutes.ts:54-56 |
Code-based; no complexity validation |
POST |
/api/auth/reset-password |
authRoutes.ts:49-52 |
Token-based variant; enforces complexity via passwordResetValidation |
Two-endpoint comparison
[!important] Code-based vs token-based reset endpoints
POST /api/auth/reset-password-with-code(primary UI path)
- Uses a 6-digit numeric code delivered by email.
isValidVerificationCode()validates with/^\d{6}$/.- Has no password complexity middleware. Any string is accepted as the new password.
POST /api/auth/reset-password(legacy token-based path)
- Uses a URL token (link in email) rather than a short code.
- Enforces password complexity via
passwordResetValidationmiddleware (requires uppercase, lowercase, and a digit).The two endpoints provide inconsistent security guarantees. Users who reset via the code flow can set a weak password that would be rejected by the token flow.
Database writes
userscollection: on request, setspasswordResetCode+passwordResetCodeExpires. On submit, replacespassword, clears reset fields, and emptiesrefreshTokens.
Socket events emitted
- None.
Side effects
- Email: one transactional message containing the 6-digit code.
- Server-side log:
authController.ts:559console.logincludes the generated code in plain text — same hardening note as Registration Flow. - Session invalidation: All refresh tokens cleared → all devices forced to re-login after password change. Access tokens still valid until expiry (typically minutes).
Error / edge cases
- Unknown email → always
200, generic message. No enumeration. - Invalid code format →
400fromisValidVerificationCodeguard before DB lookup. - Expired code (>1h) →
400 Invalid or expired reset code. - Multiple parallel requests → each overwrites the previous
passwordResetCode; the latest email wins, prior codes silently invalidated. - User attempts reset on deleted account → treated as unknown (no email sent,
200returned). - Email delivery failure → response still
200; user can request again. - Access tokens still valid post-reset → unavoidable with stateless JWT; mitigated by short TTL. Critical operations should re-verify password.
[!warning] Plaintext code in logs Same as Registration Flow: the reset code is
console.log-ed by the controller in all environments. Restrict log access in production or gate the log behindNODE_ENV !== 'production'.
Known issues summary
| Issue | Severity | Details |
|---|---|---|
| No password complexity on code-based reset | Security gap | POST /api/auth/reset-password-with-code has no complexity middleware; weak passwords accepted |
| Inconsistent complexity between reset endpoints | Security gap | Token-based reset enforces complexity; code-based reset does not |
Linked flows
- Authentication Flow — user re-signs-in after reset.
- Registration Flow — same code-generation utility.
Source files
- Backend:
backend/src/services/auth/authController.ts:542-657 - Backend:
backend/src/services/email/emailService.ts(sendPasswordResetCodeEmail) - Frontend:
frontend/src/auth/view/jwt/jwt-reset-password-view.tsx - Frontend:
frontend/src/auth/view/jwt/jwt-update-password-view.tsx - Frontend:
frontend/src/auth/context/jwt/action.ts:181-200,:261-276