Files
nick-doc/01 - Architecture/Security Architecture.md
Siavash Sameni 940ad0c655 Add full system audit reports and Telegram Mini App debug handoff
- Three-stream audit (security / logic / performance) with 35+ findings
  derived from actual source code, each with file:line and remediation
- Audit Index cross-references criticals across streams into prioritized
  fix tiers: immediately / before soft launch / before public launch
- Telegram Mini App debug handoff documenting what was implemented and
  all remaining work items with exact file lists and test commands
- Updated architecture, data model, auth API, and registration flow docs
  to reflect Telegram auth, TON wallet, and email verification additions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 17:20:08 +04:00

13 KiB

title, tags, created
title tags created
Security Architecture
architecture
security
authentication
rbac
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
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 (SHKeeper, 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)

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)

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

  • MiddlewareauthMiddleware (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.
  • UIAuthGuard + 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 SHKeeper

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
  • Raw body must be used for HMAC — express.raw({ type: 'application/json' }) is mounted on this route only (before the global express.json() parser).
  • 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.

5.2 Request Network

  • 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.

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 Payment Flow - SHKeeper for the SHKeeper full flow.


6. Input validation

  • Backendexpress-validator per route (e.g., authValidation.ts), centralised validate middleware that 422s on failure with { details: [...] }.
  • Frontendzod 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

  • 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)

  • Enable rate-limit middleware (done 2026-05-24)
  • 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