Files
nick-doc/04 - Flows/Registration Flow.md
Siavash Sameni 7a616744f4 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>
2026-05-29 15:15:02 +04:00

14 KiB
Raw Blame History

title, tags, related_models, related_apis
title tags related_models related_apis
Registration Flow
flow
auth
signup
email-verification
referral
User
TempVerification
POST /api/auth/register
POST /api/auth/verify-email-code
POST /api/auth/resend-verification

Registration Flow

Last updated: 2026-05-29 — aligned with code (see Doc vs Code Audit Report)

End-to-end specification for email + password registration with role selection (buyer/seller), six-digit email verification, optional referral code attribution, and terms acceptance.

Actors

  • Prospective User submits the sign-up form.
  • Frontend frontend/src/auth/view/jwt/jwt-sign-up-view.tsx, calling signUp() and later verifyEmailWithCode() from frontend/src/auth/context/jwt/action.ts.
  • Backend AuthController.register and AuthController.verifyEmailWithCode in backend/src/services/auth/authController.ts.
  • MongoDB TempVerification collection (temporary), then User collection (final).
  • Email service backend/src/services/email/emailService.ts (SMTP/transactional provider) — sendVerificationCodeEmail().
  • Socket.IO emits referral-signup to the referrer if a referral code is supplied.

Preconditions

  • The email is not already a verified User. If a TempVerification already exists, its code and metadata are regenerated and resent rather than throwing a conflict.
  • Outbound SMTP credentials are configured (EMAIL_* env vars consumed by emailService.ts).
  • If a referralCode is supplied, it does not need to exist for sign-up to succeed — invalid codes are silently ignored at verification time.

State machine: TempVerification → User

stateDiagram-v2
    [*] --> NotStarted
    NotStarted --> TempCreated: POST /api/auth/register\nemail + role [+ ref]
    TempCreated --> TempCreated: POST /api/auth/resend-verification\n(new code, 15-min TTL)
    TempCreated --> TempExpired: 15 minutes elapse\nor verification fails
    TempExpired --> TempCreated: User clicks "Resend"
    TempCreated --> UserActive: POST /api/auth/verify-email-code\n(code + password)
    UserActive --> [*]
    note right of TempCreated
        TempVerification document holds:
        email, firstName, lastName, role,
        referralCode, code, codeExpires
    end note
    note right of UserActive
        User created with isEmailVerified=true,
        status="active"; tokens issued immediately.
    end note

Step-by-step narrative

Phase 1 — Submit registration

  1. User visits /auth/jwt/sign-up (optionally with ?ref=ABCD1234 from the short-URL referral redirect implemented at backend/src/app.ts:274-278).
  2. User selects role (buyer or seller), enters email, password (held in client state only), accepts the Terms checkbox, and clicks "Create account".

[!bug] ⚠️ KNOWN BUG / quirk — the sign-up form does not collect the real password jwt-sign-up-view.tsx onSubmit calls signUp({ ..., password: '' }) with a hard-coded empty string (jwt-sign-up-view.tsx:191, with the inline comment // You might need to add password field to form). So the actual password is not collected on the sign-up form at all — it is collected at the email-verification step (/verify-email-code). The TempVerification.password field is effectively unused (it is set to '' and never read as a real credential). The credential that ends up on the User is the one entered at verification.

  1. HTTP request: POST /api/auth/register with { email, password: '', firstName?, lastName?, role, referralCode? }. The frontend passes password: '' (empty string) — see the quirk above. The controller persists this empty string into TempVerification.password, which is never used as a real credential.
  2. Validation middleware registerValidation (authValidation.ts) checks email format, password complexity, and role enum.
  3. Duplicate check (authController.ts:55-64): User.findOne({ email }) — if found, returns 409 USER_EXISTS.
  4. Idempotent temp record: TempVerification.findOne({ email }) — if present, the existing temp is updated in place (new name, role, referralCode, fresh 6-digit code, expiry pushed to now + 15 min).
  5. Verification code: authService.generateVerificationCode() (authService.ts:226-228) returns a uniformly random 6-digit string.
  6. Persistence: A new TempVerification is saved with { email, password: '', firstName: defaults to "کاربر", lastName: defaults to "جدید", role, referralCode, emailVerificationCode, emailVerificationCodeExpires }.
  7. Email dispatch: emailService.sendVerificationCodeEmail(email, firstName, code) is called. The email contains the 6-digit code, branding, and a 15-minute expiry notice. Failure to send is logged but the response still succeeds with 201 (the user can resend).
  8. Response: { email, message: "Verification code sent to email" } with HTTP 201 for first-time, 200 for resend.
  9. Frontend transitions to the OTP screen /auth/jwt/verify?email=... (frontend/src/auth/view/jwt/jwt-verify-view.tsx).

Phase 2 — Verify code and finalise

  1. User enters the 6-digit code and confirms the password. The password may be re-entered here for safety.
  2. HTTP request: POST /api/auth/verify-email-code with { email, code, password }.
  3. Format guard: authService.isValidVerificationCode(code) enforces /^\d{6}$/ (authService.ts:236-238).
  4. Lookup: TempVerification.findOne({ email, emailVerificationCode: code, emailVerificationCodeExpires: { $gt: now } }) — if any field mismatches or the code is older than 15 minutes, returns 400.
  5. Hash password: bcrypt.hash(password, 12) via authService.hashPassword().
  6. Create User (authController.ts:400-435): email, password: hashedPassword, firstName, lastName, role, isEmailVerified: true, status: "active".
  7. Apply referral (authController.ts:691-713): tempVerification.referralCode (stored on the TempVerification document at registration and applied here at verification) is looked up via User.findOne({ referralCode }). If a referrer is found:
    • user.referredBy = referrer._id
    • referrer.referralStats.totalReferrals += 1
    • Emit referral-signup on user-${referrer._id} Socket.IO room (authController.ts:704; the equivalent Google/other path emits at authController.ts:1132) — see Referral Flow for the points-awarding side effect that happens later on the first purchase.
    • ⚠️ No self-referral guard: the code only checks if (referrer) — it never compares referrer._id to the newly created user. A user who somehow signs up with their own referralCode would be attributed as their own referrer.
  8. Persist user, then delete the TempVerification document (findByIdAndDelete).
  9. Token issuance: identical to Authentication Flow — generate access + refresh, push the refresh into user.refreshTokens[].
  10. Response: { user, tokens: { accessToken, refreshToken } }. Frontend writes both into localStorage (action.ts:228-235) and routes the user into the appropriate dashboard (/dashboard/buyer or /dashboard/seller).

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
    participant IO as Socket.IO

    U->>FE: Fill sign-up form (email, role, ref?, password)
    FE->>BE: POST /api/auth/register
    BE->>DB: User.findOne({ email })
    DB-->>BE: null
    BE->>DB: TempVerification.findOne({ email })
    DB-->>BE: null
    BE->>BE: code = generateVerificationCode()
    BE->>DB: TempVerification.create({...code, expires=+15m})
    BE->>MAIL: sendVerificationCodeEmail(email, firstName, code)
    MAIL-->>U: Email with 6-digit code
    BE-->>FE: 201 { email, message }
    FE-->>U: Redirect /auth/jwt/verify

    U->>FE: Enter code + (re)password
    FE->>BE: POST /api/auth/verify-email-code { email, code, password }
    BE->>DB: TempVerification.findOne({ email, code, expires>now })
    DB-->>BE: tempVerification doc
    BE->>BE: hashPassword(password)
    BE->>DB: User.create({...isEmailVerified:true, status:active})
    opt referral present
        BE->>DB: User.findOne({ referralCode })
        DB-->>BE: referrer
        BE->>DB: referrer.referralStats.totalReferrals += 1
        BE->>IO: emit user-{refId} 'referral-signup'
    end
    BE->>DB: TempVerification.findByIdAndDelete(...)
    BE->>BE: generate tokens
    BE->>BE: push refresh
    BE-->>FE: 200 { user, tokens }
    FE->>FE: localStorage.setItem(accessToken, refreshToken)
    FE-->>U: Redirect /dashboard/{role}

API calls

Method Endpoint Source
POST /api/auth/register authRoutes.ts:21authController.register
POST /api/auth/verify-email-code authRoutes.ts:34authController.verifyEmailWithCode
POST /api/auth/resend-verification authRoutes.ts:36-40authController.resendVerificationEmail
GET /r/:code app.ts:274-278 — short-URL redirect that injects ?ref= into the sign-up page
POST /api/auth/force-verify-user Dev-only — authController.forceVerifyUser (rejects outside NODE_ENV=development)

Database writes

  • tempverifications collection: insert on first POST (carrying email, password: '', firstName, lastName, role, referralCode, code + expiry), in-place update on duplicate POST, delete on successful verification.
  • users collection: full insert on successful verification (authController.ts:680-688). The first refresh token is appended in the same save.
  • users collection (referrer): referralStats.totalReferrals incremented (authController.ts:699).

Socket events emitted

  • referral-signupuser-${referrerId} room when a referred user verifies. Payload:
    { userId, userName, userEmail, timestamp, totalReferrals }
    
    Source: authController.ts:704-710 (and :1132 on the parallel path).

Side effects

  • Email: one transactional message per /register and per /resend-verification. Content is generated by emailService.sendVerificationCodeEmail. Plain-text fallback included.
  • Sentry: errors during User.create or email dispatch are captured server-side.
  • Logs: the controller console.logs the generated code in all environments (authController.ts:88, :117, :518). Useful in dev; in prod the same log line ends up in CloudWatch/Sentry breadcrumbs. (Tracked as a hardening item.)

[!warning] Verification code is logged server-side The generated 6-digit code is console.log-ed by the controller even in production. Anyone with log access can take over an unverified account. Move behind if (NODE_ENV !== 'production').

Error / edge cases

  • Email already registered (verified)409 USER_EXISTS.
  • Email already in temp (unverified)200, code regenerated, email re-sent. User-friendly; no error.
  • Code mismatch / expired (>15 min)400 Invalid or expired verification code. The TempVerification is not deleted, so the user can request a new code via "Resend".
  • Code format wrong (non-digits or wrong length)400 from isValidVerificationCode guard before DB lookup.
  • Email delivery failure → response still 201/200; the user can hit "Resend" or check spam.
  • Referral code that does not match any user → silently ignored; the user is still created with referredBy: undefined.
  • Self-referralnot guarded. The referral attribution (authController.ts:691-713) only checks that a referrer exists, never that it differs from the signing-up user.
  • Race condition: two parallel registrations for the same email → MongoDB unique index on User.email ensures only one user document; the loser of the race sees E11000 and returns 409 USER_EXISTS.
  • Race condition: verify request arrives twice with the same code → second request finds no TempVerification and returns 400. The created User is the canonical record.
  • Role tampering → role is validated by registerValidation enum (buyer | seller). Admin role is created only via the bootstrap seed (initializeAdminUser in app.ts:377), never via this flow.

Defaults & quirks

  • firstName / lastName are not required by the frontend in many sign-up variants; the controller defaults them to Persian placeholders "کاربر" / "جدید" (authController.ts:52-53). They can be edited later under /dashboard/account/profile.
  • The TempVerification TTL is enforced by the emailVerificationCodeExpires check, not by a Mongo TTL index — expired docs remain in the collection until overwritten or manually purged.

Linked flows

  • Authentication Flow — the next time the user signs in (includes the Telegram first-class auth section).
  • Referral Flow — full points-awarding mechanics triggered here.
  • Google OAuth Flow — alternative path that bypasses TempVerification (Google identities are pre-verified).
  • Password Reset Flow — if the user forgets the password they set during verification.

[!tip] Telegram — zero-step registration Users who open the Amanat Telegram Mini App do not go through this flow at all. POST /api/auth/telegram verifies the Telegram-signed initData and auto-provisions a new User (no email, authProvider: "telegram") in a single round-trip. The TempVerification + email code cycle only applies to email-based sign-ups. See Authentication Flow#Telegram first-class auth flow.

Source files

  • Backend: backend/src/services/auth/authController.ts:33-158 (register), :364-469 (verify), :498-539 (resend)
  • Backend: backend/src/services/auth/authValidation.ts (validation rules)
  • Backend: backend/src/models/TempVerification.ts (temp schema)
  • Backend: backend/src/services/email/emailService.ts (sendVerificationCodeEmail)
  • Backend: backend/src/app.ts:274-278 (short referral redirect)
  • Frontend: frontend/src/auth/view/jwt/jwt-sign-up-view.tsx
  • Frontend: frontend/src/auth/view/jwt/jwt-verify-view.tsx
  • Frontend: frontend/src/auth/context/jwt/action.ts:121-256