first commit
This commit is contained in:
commit
34ea15a54d
246 changed files with 18660 additions and 0 deletions
194
docs/SECURITY.md
Normal file
194
docs/SECURITY.md
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
# 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue