--- title: Registration Flow tags: [flow, auth, signup, email-verification, referral] related_models: ["[[User]]", "[[TempVerification]]"] related_apis: ["POST /api/auth/register", "POST /api/auth/verify-email-code", "POST /api/auth/resend-verification"] --- # Registration Flow 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` ```mermaid 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". > [!tip] Password is **not** sent to `/register` > The password is only included in the second step (`/verify-email-code`). The intent: never hash and store a password for an unverified account. The TempVerification document carries `password: ''` until verification. 3. **HTTP request**: `POST /api/auth/register` with `{ email, password?, firstName?, lastName?, role, referralCode? }`. (The frontend currently passes the password through, but the controller stores `''` regardless — see `authController.ts:123`.) 4. **Validation middleware** `registerValidation` (`authValidation.ts`) checks email format, password complexity, and role enum. 5. **Duplicate check** (`authController.ts:55-64`): `User.findOne({ email })` — if found, returns `409 USER_EXISTS`. 6. **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). 7. **Verification code**: `authService.generateVerificationCode()` (`authService.ts:226-228`) returns a uniformly random 6-digit string. 8. **Persistence**: A new `TempVerification` is saved with `{ email, password: '', firstName: defaults to "کاربر", lastName: defaults to "جدید", role, referralCode, emailVerificationCode, emailVerificationCodeExpires }`. 9. **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). 10. **Response**: `{ email, message: "Verification code sent to email" }` with HTTP `201` for first-time, `200` for resend. 11. **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 12. **User enters the 6-digit code** and confirms the password. The password may be re-entered here for safety. 13. **HTTP request**: `POST /api/auth/verify-email-code` with `{ email, code, password }`. 14. **Format guard**: `authService.isValidVerificationCode(code)` enforces `/^\d{6}$/` (`authService.ts:236-238`). 15. **Lookup**: `TempVerification.findOne({ email, emailVerificationCode: code, emailVerificationCodeExpires: { $gt: now } })` — if any field mismatches or the code is older than 15 minutes, returns `400`. 16. **Hash password**: `bcrypt.hash(password, 12)` via `authService.hashPassword()`. 17. **Create `User`** (`authController.ts:400-435`): `email`, `password: hashedPassword`, `firstName`, `lastName`, `role`, `isEmailVerified: true`, `status: "active"`. 18. **Apply referral** (`authController.ts:411-433`): if `tempVerification.referralCode` exists, find the referrer by `User.findOne({ referralCode })`. If found: - `user.referredBy = referrer._id` - `referrer.referralStats.totalReferrals += 1` - Emit `referral-signup` on `user-${referrer._id}` Socket.IO room — see [[Referral Flow]] for the points-awarding side effect that happens later on the first purchase. 19. **Persist user**, then **delete** the TempVerification document (`findByIdAndDelete`). 20. **Token issuance**: identical to [[Authentication Flow]] — generate access + refresh, push the refresh into `user.refreshTokens[]`. 21. **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 ```mermaid 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; 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:21` → `authController.register` | | `POST` | `/api/auth/verify-email-code` | `authRoutes.ts:34` → `authController.verifyEmailWithCode` | | `POST` | `/api/auth/resend-verification` | `authRoutes.ts:36-40` → `authController.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, in-place update on duplicate POST (`authController.ts:66-108`), delete on successful verification. - **`users` collection**: full insert on successful verification (`authController.ts:400-435`). The first refresh token is appended in the same save. - **`users` collection (referrer)**: `referralStats.totalReferrals` incremented (`authController.ts:419`). ## Socket events emitted - **`referral-signup`** → `user-${referrerId}` room when a referred user verifies. Payload: ``` { userId, userName, userEmail, timestamp, totalReferrals } ``` Source: `authController.ts:423-431`. ## 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.log`s 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`. - **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. - [[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. ## 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`