230 lines
9.8 KiB
Markdown
230 lines
9.8 KiB
Markdown
---
|
|
title: Security Architecture
|
|
tags: [architecture, security, authentication, rbac]
|
|
created: 2026-05-23
|
|
---
|
|
|
|
# Security Architecture
|
|
|
|
How identity, authorization, transport, and integrity are handled across the platform.
|
|
|
|
> [!important]
|
|
> Read alongside [[Authentication Flow]] (user-facing), [[Passkey (WebAuthn) Flow]], and [[Payment Flow - SHKeeper]] (webhook HMAC).
|
|
|
|
---
|
|
|
|
## 1. Threat model — at a glance
|
|
|
|
| Threat | Mitigation |
|
|
|---|---|
|
|
| Credential stuffing | bcrypt 12-round hashing + account lockout + rate-limit (when enabled) |
|
|
| Session hijacking | Short-lived JWTs (7d), opaque refresh tokens (30d), token rotation |
|
|
| CSRF | JWT in `Authorization` header (not cookie), CORS allow-list |
|
|
| XSS | Helmet CSP, React auto-escaping, sanitize HTML before storage |
|
|
| SQL/NoSQL injection | Mongoose parameterized queries, no `$where` strings, schema validation |
|
|
| Webhook spoofing | HMAC SHA-256 over body + secret, constant-time compare |
|
|
| File upload abuse | Multer MIME validation, 5 MB cap, non-executable storage, served by Nginx not Node |
|
|
| Replay attacks | Per-payment idempotency on `providerPaymentId`, per-request `X-Request-Id` |
|
|
| Account takeover | Email verification required, password reset code expiry (1h), passkey support |
|
|
| Phishing | Passkey origin binding (`NEXT_PUBLIC_PASSKEY_ORIGIN`), email domain pinning |
|
|
| Data leakage | Role-gated endpoints, field-level projection (`select: false` on password), redacted logs |
|
|
|
|
---
|
|
|
|
## 2. Authentication layers
|
|
|
|
### 2.1 Email + password (primary)
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
actor U as User
|
|
participant FE as Frontend
|
|
participant BE as Backend
|
|
participant DB as MongoDB
|
|
U->>FE: enters credentials
|
|
FE->>BE: POST /api/auth/login { email, password }
|
|
BE->>DB: User.findOne({ email })
|
|
DB-->>BE: user doc (incl. hashed password)
|
|
BE->>BE: bcrypt.compare(password, hash)
|
|
alt invalid
|
|
BE->>DB: increment loginAttempts
|
|
BE->>DB: lock account at N
|
|
BE-->>FE: 401 / 423 locked
|
|
else valid
|
|
BE->>BE: sign JWT (7d), refresh (30d)
|
|
BE->>DB: store refresh-token id
|
|
BE->>DB: clear attempts
|
|
BE-->>FE: 200 { user, token, refreshToken }
|
|
end
|
|
```
|
|
|
|
- Password rules enforced by `authValidation.ts`: ≥8 chars, mixed case + digit recommended (cite the validator for exact rules).
|
|
- bcrypt rounds = 12 (`authService.ts`).
|
|
- Lockout: after N failed attempts within window, account locked for cooldown — see `authService.ts:113-145`.
|
|
- Reset code emailed on `passwordResetCode` request; valid 1h.
|
|
|
|
### 2.2 Google OAuth 2.0
|
|
|
|
- Frontend uses `NEXT_PUBLIC_GOOGLE_CLIENT_ID` for the Sign-In with Google button.
|
|
- ID token sent to backend → `googleOAuthService.ts` verifies via Google's public keys → either links to existing User by email or creates a new one.
|
|
- See [[Google OAuth Flow]].
|
|
|
|
### 2.3 WebAuthn / Passkey
|
|
|
|
- Standards-based passwordless.
|
|
- Backend: `passkeyService.ts` orchestrates registration and assertion challenges.
|
|
- Frontend env: `NEXT_PUBLIC_PASSKEY_RP_NAME=Amn`, `NEXT_PUBLIC_PASSKEY_RP_ID=<domain>`, `NEXT_PUBLIC_PASSKEY_ORIGIN=<origin>`.
|
|
- See [[Passkey (WebAuthn) Flow]].
|
|
|
|
> [!warning]
|
|
> Dev env files ship `NEXT_PUBLIC_PASSKEY_RP_ID=localhost`. In production this MUST be the actual eTLD+1 domain (e.g., `amn.gg`) — passkeys are scoped to the RP ID and can't be transferred.
|
|
|
|
### 2.4 Refresh-token rotation
|
|
|
|
- On `POST /api/auth/refresh`, the backend:
|
|
- Verifies the supplied refresh token.
|
|
- Issues a NEW access token + a NEW refresh token.
|
|
- Invalidates the old refresh token id in MongoDB.
|
|
- If the same refresh token is presented twice → all sessions for that user are invalidated (token reuse detection).
|
|
|
|
---
|
|
|
|
## 3. Authorization (RBAC)
|
|
|
|
### 3.1 Roles
|
|
|
|
| Role | Source | Capabilities |
|
|
|---|---|---|
|
|
| `buyer` (user) | default on signup | Create requests, pay, chat, dispute, rate |
|
|
| `seller` (owner) | chosen at signup OR upgraded | Make offers, build templates, run a shop, withdraw |
|
|
| `admin` | seed / manual | Moderate, mediate disputes, manage users/blogs/levels |
|
|
| `support` | seed / manual | Read-only on most data, can reset passwords, escalate |
|
|
|
|
A single User may be `buyer` and `seller` simultaneously (combined role).
|
|
|
|
### 3.2 Enforcement points
|
|
|
|
- **Middleware** — `authMiddleware` (verifies JWT) followed by `roleGuard(role)` on every route that requires elevation.
|
|
- **Service layer** — defensive `assertRole(ctx, 'admin')` calls inside critical service methods so even mis-mounted routes can't bypass.
|
|
- **UI** — `AuthGuard` + `EmailVerificationGuard` + role-aware nav (`components/nav-section`) hide admin/seller menus for users without permission. This is convenience only — never the security boundary.
|
|
|
|
---
|
|
|
|
## 4. Transport security
|
|
|
|
- **HTTPS** terminated upstream (CloudFlare / external Nginx). Internal cluster is HTTP.
|
|
- **HSTS** header set by upstream proxy (recommended `max-age=31536000; includeSubDomains; preload`).
|
|
- **CORS** — exactly one origin allowed: `config.frontendUrl`. `credentials: true`.
|
|
- **CSP** — Helmet default, currently permissive for Web3 popup compatibility (see `frontend/next.config.ts` setting COOP=`same-origin-allow-popups`, COEP=`unsafe-none`).
|
|
|
|
---
|
|
|
|
## 5. Webhook integrity (SHKeeper)
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant SHK
|
|
participant BE
|
|
SHK->>BE: POST /api/payment/shkeeper/webhook<br/>X-Signature: sha256=<hmac>
|
|
BE->>BE: hmac = HMAC_SHA256(SHKEEPER_WEBHOOK_SECRET, body)
|
|
BE->>BE: crypto.timingSafeEqual(hmac, providedSig)
|
|
alt mismatch
|
|
BE-->>SHK: 401 Unauthorized
|
|
else match
|
|
BE->>BE: process payment update
|
|
BE-->>SHK: 200 OK
|
|
end
|
|
```
|
|
|
|
- Body must be the raw bytes used for HMAC — apply `express.raw({ type: 'application/json' })` on the webhook route ONLY (the rest of the app uses parsed JSON).
|
|
- In dev (`NODE_ENV === 'development'`) signature verification can be bypassed for local testing — confirm this is gated and never reachable in prod.
|
|
- Idempotency: identical webhook delivered twice should be no-op. Check by `(providerPaymentId, status)` tuple before mutating.
|
|
|
|
See [[Payment Flow - SHKeeper]] for the full flow.
|
|
|
|
---
|
|
|
|
## 6. Input validation
|
|
|
|
- **Backend** — `express-validator` per route (e.g., `authValidation.ts`), centralised `validate` middleware that 422s on failure with `{ details: [...] }`.
|
|
- **Frontend** — `zod` schemas via `@hookform/resolvers/zod`. Same schema can be re-exported to a `shared/` package for true single-source-of-truth (not yet wired).
|
|
- **Mongoose** — schema-level `type`, `required`, `enum`, `min`/`max`, custom `validate` functions as a last line of defence.
|
|
|
|
---
|
|
|
|
## 7. File upload safety
|
|
|
|
- Stored under `uploads/{avatars|documents|products|temp}/` — non-executable, served by Nginx (no Node interpretation).
|
|
- MIME allow-list in `fileService.ts`: images for avatars/products, PDFs/docs for evidence.
|
|
- 5 MB hard cap (`MAX_FILE_SIZE=5242880`).
|
|
- Original filenames hashed → no path traversal, no clobber.
|
|
- Recommended: virus scan via ClamAV before exposing to other users (dispute evidence, chat attachments).
|
|
|
|
---
|
|
|
|
## 8. Secrets management
|
|
|
|
- Production secrets injected via host `.env`, mounted into compose `env_file`.
|
|
- Never log secrets — logger redaction recommended (winston/pino formatter).
|
|
- `.env*` files in `.gitignore`. Repo includes only `.env.development` / `.env.production` templates with **public** values (NEXT_PUBLIC_*).
|
|
- Rotate `JWT_SECRET` invalidates all existing JWTs — schedule a maintenance window.
|
|
- Rotate `SHKEEPER_WEBHOOK_SECRET` coordinated with SHKeeper dashboard (set new → verify → remove old).
|
|
|
|
See [[Environment Variables]] for the catalog.
|
|
|
|
---
|
|
|
|
## 9. Rate limiting & abuse
|
|
|
|
- Backend has `express-rate-limit` ready but currently disabled (`app.ts:227`).
|
|
- Recommended pre-launch settings:
|
|
- `/api/auth/*` — 10 req / 5 min / IP
|
|
- `/api/auth/login` — 5 req / 5 min / IP **and** /email
|
|
- global API — 100 req / 15 min / IP (current default constants)
|
|
- Counters stored in Redis when enabled.
|
|
- For chat and notifications, debounce at the client to avoid spamming legitimate emits.
|
|
|
|
---
|
|
|
|
## 10. Audit logging
|
|
|
|
The codebase currently uses `morgan` (HTTP access logs) and ad-hoc `logger.info/warn/error`. For PCI-adjacent operations (payments) consider:
|
|
|
|
- Append-only audit log of every payment / payout / refund / role change.
|
|
- Include actor (userId), target, action, before/after diff, request id.
|
|
- Persist in a separate Mongo collection or external log sink with retention ≥1y.
|
|
|
|
---
|
|
|
|
## 11. Frontend session storage
|
|
|
|
- JWT and refresh token stored in `localStorage` (per current implementation — cite to verify in `frontend/src/lib/`).
|
|
- Risk: XSS = total takeover. Mitigations: strict CSP, no `dangerouslySetInnerHTML` on untrusted content, audit dependencies (`yarn audit`).
|
|
- Alternative: store refresh token in `httpOnly` cookie and keep only short-lived access token in memory — recommended for production hardening.
|
|
|
|
---
|
|
|
|
## 12. Hardening checklist (pre-launch)
|
|
|
|
- [ ] Enable rate-limit middleware
|
|
- [ ] Promote refresh tokens to `httpOnly` cookies
|
|
- [ ] Replace `localhost` passkey RP ID with production domain
|
|
- [ ] Disable `NEXT_PUBLIC_IS_DEVELOPMENT=true` and `ENABLE_DEBUG=true` in prod build
|
|
- [ ] Verify `NODE_ENV=production` in backend prod env
|
|
- [ ] Pin production Watchtower to versioned tag (not `latest`)
|
|
- [ ] Add backend Sentry SDK + source maps
|
|
- [ ] Rotate all dev-seeded credentials before public launch
|
|
- [ ] Run `yarn audit` / `npm audit` and triage CVEs
|
|
- [ ] Pentest the payment + dispute flows specifically
|
|
- [ ] Review every `> [!warning]` callout in this vault
|
|
|
|
---
|
|
|
|
## Related
|
|
|
|
- [[Authentication Flow]] · [[Google OAuth Flow]] · [[Passkey (WebAuthn) Flow]] · [[Password Reset Flow]]
|
|
- [[Backend Architecture]] · [[Frontend Architecture]] · [[Real-time Layer]]
|
|
- [[Payment Flow - SHKeeper]] — webhook HMAC details
|
|
- [[Environment Variables]] — secret catalog
|
|
- [[Incident Response]] — what to do when something goes wrong
|