Desi-Clans/docs/SECURITY.md
2026-10-08 02:52:58 +05:30

6.2 KiB
Raw Permalink Blame History

Security — Desi Clans

Last reviewed: 2026-04-04

This document describes security assumptions, controls, and operational requirements for the Desi Clans monorepo (FastAPI API + React SPA).


Threat model (summary)

Asset Primary risks
User accounts Credential stuffing, session theft
Admin panel Privilege abuse, catalog tampering
Orders / payments Price tampering, replay, webhook forgery
Digital tickets QR leakage, double entry, scanner abuse
Media uploads Malware, path traversal (legacy paths only)

Out of scope for v1: DDoS at scale (use CDN/WAF in production), PCI DSS Level 1 (Razorpay.js handles card data).


Authentication & JWT

Attendees (/api/auth/*)

  • Passwords hashed with bcrypt (passlib). Plain passwords never stored or returned.
  • JWT HS256, claim type: "user", sub = numeric user id.
  • Register/login: Pydantic validation (email normalized, password ≥ 8 characters).
  • Rate limit: 20 login / 10 register requests per IP per minute (in-process).

Admins (/api/admin/auth/*)

  • Separate JWT claim type: "admin"; attendee tokens cannot call admin routes (tested).
  • Roles: editor, super_admin — all admin API routes currently require editor or above.
  • Rate limit: 20 login attempts per IP per minute.

Frontend token storage

  • Attendee token: localStorage (desiclans_access_token).
  • Admin token: separate key (desiclans_admin_token).
  • Assumption: XSS on the marketing or admin origin equals token compromise. Mitigate with CSP, dependency hygiene, and short TTL if needed.

Production configuration

  • SECRET_KEY ≥ 32 chars, not on blocklist (config.py).
  • ACCESS_TOKEN_EXPIRE_MINUTES bounded 5–20160.

Authorization

Endpoint class Enforcement
Public catalog No auth
Orders, tickets (owner) CurrentUser + user_id match on orders/tickets
Gate verify X-Check-In-Key (+ rate limit)
Admin CRUD EditorAdmin JWT
Admin ticket verify Editor JWT + same gate key as public verify

QR reuse: verify_ticket_at_gate locks ticket row (SELECT … FOR UPDATE), sets used once; second scan returns valid: false (HTTP 200).

Order manipulation: Clients send event_id + line items only; prices and totals come from TicketType rows under row lock. Ticket types must belong to the event.

Payment verify: User must own order; provider_order_id must match order; signature verified via Razorpay/mock HMAC; amount/currency compared to order.total_paise.


Payments & webhooks

Provider Use
mock Local dev / CI only
razorpay Production
  • POST /api/webhooks/mock returns 404 unless ENVIRONMENT=development.
  • Razorpay webhooks: signature verified (RAZORPAY_WEBHOOK_SECRET).
  • Webhook events stored with idempotency on provider_event_id.
  • POST …/payment/mock-complete only when development + mock provider.

Production startup validation (Settings.production_hardening):

  • PAYMENT_PROVIDER=razorpay with Razorpay secrets set.
  • TICKET_VERIFY_API_KEY ≥ 16 characters.
  • ADMIN_PASSWORD not default.
  • CORS_ORIGINS non-empty.

CORS

  • Explicit origin list from CORS_ORIGINS (no *).
  • allow_credentials=True — do not add untrusted origins in production.

SQL injection

  • Business logic uses SQLAlchemy ORM / parameterized queries.
  • Raw SQL limited to health check SELECT 1 and Alembic migrations.

Input validation

  • Request bodies use Pydantic schemas (schemas/*).
  • Ticket verify: qr_token length bounds.
  • Order lines: quantity 1–20, max 20 lines.

File uploads

  • Active /api router: no upload endpoints.
  • Legacy app/routers/admin.py (not mounted): extension whitelist, 8MB cap, PIL re-encode to WebP.
  • Assumption: Keep legacy routers unmounted or remove before exposing uploads on production API.

Secrets & environment

Variable Notes
SECRET_KEY JWT signing; rotate with forced re-login
PAYMENT_MOCK_SECRET Dev only
RAZORPAY_* Production payments
TICKET_VERIFY_API_KEY Gate scanners; constant-time compare
DATABASE_URL Never commit; least-privilege DB user

See backend/.env.example and frontend/.env.example.


API error leakage

  • Production: debug=False, OpenAPI disabled, generic 500 body.
  • AppError returns intentional detail strings only.
  • Development may expose exception text on 500.

Rate limiting

In-process sliding window (app/core/rate_limit.py) on:

  • Auth login/register
  • Admin login
  • Ticket verify
  • Payment verify

Limitation: Per-process only; use reverse-proxy or WAF limits for multi-instance deployments.


QR codes

  • Opaque qr_token (url-safe random); payload is JSON {"v":1,"token":…} without embedded HMAC.
  • Security relies on HTTPS, token secrecy, and server-side verify.
  • Ticket detail (GET /api/tickets/{id}) owner-only; includes QR image for valid paid orders.

Frontend (checkout)

  • Razorpay key id only in browser; secrets stay on server.
  • No payment card data touches Desi Clans backend.

Scanner / admin verify UI

  • Staff UI calls POST /api/tickets/verify with optional VITE_TICKET_VERIFY_API_KEY → X-Check-In-Key header.
  • In development, if TICKET_VERIFY_API_KEY is unset, verify is open (local convenience only).

Checklist before production

  1. Set strong SECRET_KEY, TICKET_VERIFY_API_KEY, Razorpay secrets, non-default ADMIN_PASSWORD.
  2. ENVIRONMENT=production, PAYMENT_PROVIDER=razorpay.
  3. Tight CORS_ORIGINS to real site origin(s).
  4. TLS everywhere; HSTS at edge.
  5. Do not mount legacy app/routers/* without security review.
  6. Enable edge rate limiting and monitoring on auth/webhook paths.

  • tests/test_security.py — JWT types, bcrypt
  • tests/test_security_hardening.py — production settings, mock webhook
  • tests/test_payments.py — verify + webhook idempotency
  • tests/test_digital_tickets.py — QR reuse
  • tests/test_admin_api.py — attendee blocked from admin