---
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]], [[Escrow Flow]], and [[Request Network Integration Constraints]].

---

## 1. Threat model — at a glance

| Threat | Mitigation |
|---|---|
| Credential stuffing | bcrypt 12-round hashing + account lockout + rate-limit |
| 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 raw body + provider secret (Request Network, Telegram), 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`; Telegram initData in-memory replay map; 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 |
| Forged Telegram identity | `initData` HMAC-SHA256 verified server-side; `initDataUnsafe` never trusted; auth_date max-age enforced |
| Telegram callback replay | In-memory replay map with configurable window in `telegramService.ts` |
| Blocked Telegram user bypass | `TelegramLink.status === 'blocked'` returns 403 regardless of unlink/re-link attempts |

---

## 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 Telegram (first-class auth provider)

```mermaid
sequenceDiagram
    actor U as User (Telegram)
    participant FE as Frontend / Mini App
    participant BE as Backend
    participant DB as MongoDB
    U->>FE: opens Mini App (initData available) or clicks Login Widget
    FE->>BE: POST /api/auth/telegram { initData | loginWidget }
    BE->>BE: verifyMiniAppInitData() or verifyTelegramLoginWidget()
    BE->>BE: reject if auth_date stale / replay / bot account
    BE->>DB: TelegramLink.findOne({ telegramUserId })
    alt link exists and active
        BE->>DB: load linked User
    else no link — auto-provision
        BE->>DB: User.create({ authProvider:"telegram", telegramVerified:true, email:null })
        BE->>DB: TelegramLink.create(...)
    end
    BE->>DB: upsert TelegramLink.lastSeenAt
    BE->>BE: generateToken() + generateRefreshToken()
    BE-->>FE: 200 { user, tokens, isNewUser }
```

- Backend source: `backend/src/services/auth/authController.ts` (`telegramAuth`) and `backend/src/services/telegram/telegramService.ts`.
- Mini App path uses `HMAC-SHA256("WebAppData", BOT_TOKEN)` per Telegram spec; Login Widget path uses `HMAC-SHA256(data_check_string, SHA256(BOT_TOKEN))`.
- In-memory replay maps guard against duplicate `initData` submissions within a configurable window.
- Blocked `TelegramLink` records return `403` — users cannot circumvent by unlinking and re-linking.
- Users with `authProvider: "telegram"` have nullable email; email-based operations (password reset) are not applicable to them.
- See [[Authentication Flow#Telegram first-class auth flow]].

### 2.5 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

### 5.1 Request Network

```mermaid
sequenceDiagram
    participant RN
    participant WK as Durable ingress (roadmap)
    participant BE
    RN->>WK: POST /api/payment/request-network/webhook<br/>x-request-network-signature
    WK->>WK: Store raw body + headers + delivery id
    WK->>BE: Forward / replay raw webhook
    BE->>BE: verifyRequestNetworkWebhookSignature(rawBody, headers)
    alt mismatch
        BE-->>WK: 401 Unauthorized
    else match
        BE->>BE: idempotency + Transaction Safety Provider
        BE->>BE: process payment update / ledger entry
        BE-->>WK: 200 OK
    end
```

- Webhooks arrive at `/api/payment/request-network/webhook` with an `x-request-network-signature` header.
- The backend verifies the signature using `backend/src/services/payment/requestNetwork/signature.ts` before any state mutation.
- The route is mounted **before** the global `express.json()` body parser so raw body bytes are available for signature computation.
- The global rate-limit middleware is configured to skip this path to avoid blocking high-frequency payment events.
- Reconciliation service (`requestNetworkReconciliationService.ts`) handles replayed or out-of-order webhooks idempotently.
- Durable ingress is the target production shape: the Worker stores delivery evidence and supports replay, but the backend remains the trust oracle.

### 5.2 Legacy SHKeeper note

SHKeeper-specific webhook docs are historical migration context. The current backend payment tree uses Request Network as the primary provider; do not reintroduce SHKeeper signature bypasses or fallback webhook heuristics without a new security review.

### 5.3 Telegram Bot webhook

- Webhooks arrive at `/api/telegram/webhook` with an `x-telegram-bot-api-secret-token` header.
- The backend verifies the token with `verifyTelegramWebhookSecret()` (`telegramService.ts`) before processing updates.
- A per-update-id in-memory replay map prevents duplicate processing within the configured window.
- The global rate-limit middleware is configured to skip this path.

See [[Escrow Flow]] and [[Request Network Integration Constraints]] for the current payment path.

---

## 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 `REQUEST_NETWORK_WEBHOOK_SECRET` coordinated with Request Network configuration (set new → verify → remove old).

See [[Environment Variables]] for the catalog.

---

## 9. Rate limiting & abuse

- Rate limiting is **enabled** as of 2026-05-24 (`app.ts`).
- Active tiers:
  - `/api/auth/*` — 10 req / 15 min / IP
  - `/api/payment/*` — 30 req / 15 min / IP
  - `/api/ai/*` — 20 req / 15 min / IP
  - global API — 100 req / 15 min / IP (skips `/health` and Request-Network webhooks)
- Counters are in-memory (Redis adapter planned for distributed deploys).
- 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)

- [x] Enable rate-limit middleware (done 2026-05-24)
- [x] Enforce Socket.IO JWT authentication (done 2026-05-24)
- [ ] 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]] (includes Telegram first-class auth flow) · [[Google OAuth Flow]] · [[Passkey (WebAuthn) Flow]] · [[Password Reset Flow]]
- [[Backend Architecture]] · [[Frontend Architecture]] · [[Real-time Layer]]
- [[Request Network Integration Constraints]] — payment webhook, checkout, and reconciliation constraints
- [[Environment Variables]] — secret catalog
- [[Incident Response]] — what to do when something goes wrong
