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

194 lines
6.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.
---
## Related tests
- `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