Initial commit: nick docs

This commit is contained in:
moojttaba
2026-05-23 20:35:34 +03:30
commit 0da235ae27
90 changed files with 18268 additions and 0 deletions

View File

@@ -0,0 +1,195 @@
---
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`