docs: align flow docs with code reality + create 35 implementation issue files
Flow docs updated (11 files): - Delivery Confirmation: reversed actor roles (buyer generates, seller verifies), fixed endpoint paths (/delivery-code/generate, /delivery-code/verify) - Passkey (WebAuthn): removed stub/simulated-key claims; real @simplewebauthn/server attestation is implemented; refresh tokens are persisted - Dispute: corrected resolve schema (action enum), removed non-existent statuses, documented security gaps (no role guards on status/resolve/assign), route shadowing, all socket events are TODO stubs - Seller Offer: corrected all endpoint paths, removed 'active' status, documented withdraw dead code, missing seller history page, select-offer notification gap - Notification: corrected mark-all-read method+path, fixed GET /:id broken lookup, added unread-count-update socket event - Authentication: corrected rate limiter (counts all attempts), axios 403 not handled, deleteAccount wrong endpoint bug, changePassword no UI - Password Reset: corrected 6-digit code (not 8), documented no-complexity gap on reset-with-code vs token reset - Payment Flow DePay: /create→/save, removed phantom sub-routes, SIM_ bypass risk, PaymentProvider type gap, getProviderIntentEndpoint routing bug - Payment Flow SHKeeper: removed phantom polling endpoint, fixed release/refund paths - Purchase Request: added pending_payment/active statuses, fixed sellers/attachments endpoints, corrected socket events, PUT→PATCH bug - Escrow: documented dispute resolve does not touch escrow, route shadowing, confirm-delivery auth gap Issues created (35 files in Issues/): - 9 security issues (critical) including: dispute privilege escalation ×4, unauthenticated payment/scanner endpoints ×2, SIM_ production bypass, confirm-delivery ownership gap - 26 additional major/critical bugs covering broken endpoints, missing features, data integrity gaps, and frontend-backend mismatches Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,9 @@ related_models: ["[[User]]", "[[TempVerification]]"]
|
||||
related_apis: ["[[Auth API]]", "POST /api/auth/login", "POST /api/auth/refresh-token", "POST /api/auth/logout"]
|
||||
---
|
||||
|
||||
> [!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.
|
||||
|
||||
# Authentication Flow
|
||||
|
||||
End-to-end specification for **email + password** authentication, JWT issuance, token lifecycle, refresh-token rotation, and logout cleanup. This is the most-used auth path in the marketplace and underpins every protected API call and Socket.IO room subscription.
|
||||
@@ -32,7 +35,7 @@ End-to-end specification for **email + password** authentication, JWT issuance,
|
||||
2. **Client-side guards**: `signInWithPassword()` (`action.ts:32-116`) verifies the browser is online and `localStorage` is writable; otherwise it throws a typed `AuthErrorHandler` error.
|
||||
3. **HTTP request**: The frontend POSTs `{ email, password }` to `POST /api/auth/login` (resolved by `endpoints.auth.login` in `frontend/src/lib/axios.ts`). An `AbortController` is armed with a 60-second timeout.
|
||||
4. **Validation middleware** runs `loginValidation` (`backend/src/services/auth/authValidation.ts`) — wires into Express via `authRoutes.ts:22`.
|
||||
5. **Rate limiting**: `rateLimitService.checkLoginAttempts(email, 5, 15*60)` is called (`authController.ts:173`). Five failures within 15 minutes returns `429 TOO_MANY_ATTEMPTS`. Counters live in Redis so they survive restarts.
|
||||
5. **Rate limiting**: `rateLimitService.checkLoginAttempts(email, 5, 15*60)` is called (`authController.ts:173`). The counter is incremented on **every login attempt** (before password comparison), not only on failures. Once 5 total attempts accumulate within a 15-minute window, the endpoint returns `429 TOO_MANY_ATTEMPTS`. The counter is reset upon a fully successful login (step 9). Counters live in Redis so they survive restarts.
|
||||
6. **User lookup**: `User.findOne({ email, status: "active" }).select("+password")` — `password` is `select: false` by default in the schema and must be explicitly projected.
|
||||
7. **Password comparison**: `authService.comparePassword()` invokes `bcrypt.compare()` (cost factor 12 — see `authService.ts:102-105`). Constant-time per bcrypt's design.
|
||||
8. **Email-verification gate**: If `!user.isEmailVerified`, returns `403 EMAIL_NOT_VERIFIED` with `needsVerification: true`. The frontend intercepts this in `action.ts:104-111` and redirects to `/auth/jwt/verify?email=...`.
|
||||
@@ -49,7 +52,7 @@ End-to-end specification for **email + password** authentication, JWT issuance,
|
||||
> [!warning] Token storage is `localStorage`, not cookies
|
||||
> Tokens are persisted in `window.localStorage`. This is intentional (the API is a separate origin and the app is fully SPA-like), but it means tokens are reachable from any script running on the page. Mitigations in place: strict CSP via `helmet`, no third-party scripts in the auth views, and short access-token TTL with refresh rotation. There are **no httpOnly auth cookies**.
|
||||
|
||||
16. **Axios interceptor** (`frontend/src/lib/axios.ts`) attaches `Authorization: Bearer ${accessToken}` to every subsequent request and, on `401/403`, automatically calls the refresh flow described below.
|
||||
16. **Axios interceptor** (`frontend/src/lib/axios.ts`) attaches `Authorization: Bearer ${accessToken}` to every subsequent request. On a `401` response, the interceptor automatically triggers the refresh flow described below. A `403` response (e.g., `EMAIL_NOT_VERIFIED`) is **not** retried via refresh — it is surfaced directly to the caller.
|
||||
17. **Socket.IO bootstrap**: After login, the dashboard layout connects to Socket.IO and emits `join-user-room`, `join-buyer-room`/`join-seller-room` based on `user.role`. See `backend/src/app.ts:83-126`.
|
||||
|
||||
## Sequence diagram
|
||||
@@ -129,19 +132,25 @@ High-risk actions are unchanged: escrow release, refund, dispute-sensitive, and
|
||||
## Side effects
|
||||
|
||||
- **Redis session**: 24-hour key holding `{ userId, email, role, ip, userAgent }`. Used for forced logout (admin can call `sessionService.deleteSession(token)`).
|
||||
- **Redis rate-limit counter**: TTL 15 min, reset on success.
|
||||
- **Redis rate-limit counter**: TTL 15 min, reset on success. Counter increments on every attempt regardless of outcome.
|
||||
- **No email** is sent on a normal login (no "new sign-in" notification today — opportunity for future enhancement).
|
||||
- **Sentry**: any unexpected exception bubbles to `Sentry.setupExpressErrorHandler` (`app.ts:351`).
|
||||
|
||||
## Refresh-token flow
|
||||
|
||||
The access token is short-lived. When a protected request returns `401 TOKEN_INVALID` or `403`, the axios interceptor calls:
|
||||
The access token is short-lived. When a protected request returns `401 TOKEN_INVALID`, the axios interceptor calls:
|
||||
|
||||
1. `POST /api/auth/refresh-token` with `{ refreshToken }` from `localStorage`.
|
||||
2. Backend `authController.refreshToken` (`:263-313`) verifies the token via `verifyRefreshToken`, checks it is **still present in `user.refreshTokens[]`**, then issues a brand-new access **and** refresh token.
|
||||
3. The old refresh token is **removed** from the array and the new one is pushed — implementing **refresh-token rotation**. A leaked-but-stale token therefore becomes invalid the moment the legitimate user refreshes.
|
||||
4. The new pair is written back to `localStorage` and the original failed request is retried.
|
||||
|
||||
> [!note] 403 responses are not retried
|
||||
> The interceptor only triggers token refresh for `status === 401`. A `403` (e.g., `EMAIL_NOT_VERIFIED`) is passed through directly to the caller without attempting a refresh.
|
||||
|
||||
> [!warning] Refresh-token sequence diagram is truncated
|
||||
> The Mermaid diagram below is **incomplete** — it was truncated in the original source at the point where the backend checks that the refresh token exists in `user.refreshTokens`. The remaining steps (rotate tokens, persist, respond, retry original request) are described in prose above but are not yet rendered in the diagram.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
@@ -154,4 +163,47 @@ sequenceDiagram
|
||||
FE->>BE: POST /api/auth/refresh-token { refreshToken }
|
||||
BE->>BE: verifyRefreshToken(refreshToken)
|
||||
BE->>DB: User.findById(decoded.id)
|
||||
BE->>DB: ensure refresh token is in user.refreshTokens
|
||||
BE->>DB: ensure refresh token is in user.refreshTokens
|
||||
Note over BE,DB: (diagram truncated — remaining steps documented in prose above)
|
||||
```
|
||||
|
||||
## Account management
|
||||
|
||||
### changePassword (API-only)
|
||||
|
||||
`POST /api/auth/change-password` exists on the backend and the `changePassword()` action is defined in `frontend/src/auth/context/jwt/action.ts`. However:
|
||||
|
||||
> [!warning] No frontend UI for change-password
|
||||
> There is **no dashboard page** that renders a change-password form. The feature is **API-only** at this time. Users cannot change their password through the UI; a developer or direct API client must call the endpoint manually.
|
||||
|
||||
### deleteAccount
|
||||
|
||||
> [!bug] Account deletion is currently broken
|
||||
> The frontend `deleteAccount` action calls `DELETE /user/profile`, which does **not exist** on the backend. The real backend endpoint is `DELETE /api/auth/account` (requires `password` in the request body and runs `deleteAccountValidation`). Until the frontend is updated to call the correct endpoint, account deletion will always fail with a 404 or routing error.
|
||||
|
||||
## Known issues summary
|
||||
|
||||
| Issue | Severity | Details |
|
||||
|---|---|---|
|
||||
| `deleteAccount` calls wrong endpoint | Bug | Frontend calls `DELETE /user/profile`; backend endpoint is `DELETE /api/auth/account` |
|
||||
| No change-password UI | Gap | `POST /api/auth/change-password` and `changePassword()` action exist but no dashboard page renders the form |
|
||||
| Rate limiter counts all attempts | Clarification | Counter increments before password check — 5 total attempts (not 5 failures) triggers lockout |
|
||||
| Axios interceptor 403 passthrough | Clarification | Interceptor only auto-refreshes on 401; 403 errors are surfaced directly |
|
||||
| Refresh-token diagram truncated | Doc debt | Mermaid diagram cut off mid-flow; prose description is authoritative |
|
||||
|
||||
## Linked flows
|
||||
|
||||
- [[Registration Flow]] — prerequisite; user must be verified.
|
||||
- [[Password Reset Flow]] — alternative credential recovery path.
|
||||
- [[Notification Flow]] — uses the issued JWT for Socket.IO room subscriptions.
|
||||
- [[Chat Flow]] — same JWT used for chat room access.
|
||||
|
||||
## Source files
|
||||
|
||||
- Backend: `backend/src/services/auth/authController.ts`
|
||||
- Backend: `backend/src/services/auth/authService.ts`
|
||||
- Backend: `backend/src/services/auth/authValidation.ts`
|
||||
- Backend: `backend/src/services/auth/authRoutes.ts`
|
||||
- Frontend: `frontend/src/auth/view/jwt/jwt-sign-in-view.tsx`
|
||||
- Frontend: `frontend/src/auth/context/jwt/action.ts`
|
||||
- Frontend: `frontend/src/lib/axios.ts`
|
||||
|
||||
Reference in New Issue
Block a user