| Header | Value | Notes |
|---|
X-Content-Type-Options | nosniff | Prevents MIME-type sniffing |
X-Frame-Options | DENY | Prevents clickjacking |
X-Request-Id | UUID | Unique per request |
Strict-Transport-Security | max-age=31536000; includeSubDomains | Production only |
| Header | Description |
|---|
X-RateLimit-Limit | Max requests in current window |
X-RateLimit-Remaining | Requests remaining |
X-RateLimit-Reset | Unix epoch when window resets |
Retry-After | Seconds until retry (on 429 only) |
Authentication Security#
Passwords hashed with argon2id (RFC 9106, memory=64MB, iterations=3, parallelism=1)
JWT access tokens: 15m, stored in JavaScript memory only (immune to CSRF)
Refresh tokens: HTTP-only cookie (SameSite=Strict; Secure; Path=/auth), immune to XSS
Token rotation on every refresh
Server-side refresh token storage (Redis) for revocation
Banning immediately invalidates all refresh tokens
Login rate limiting: 5/min/IP
Four Token Types#
| Token | Lifetime | Storage | Purpose |
|---|
| Access | 15m | JS memory | API authentication |
| Refresh | 7d | HTTP-only cookie + Redis | Silent token renewal |
| Collab | Session | Returned on room join | Yjs WebSocket auth |
| Media | 1h | Returned by /media/token | LiveKit WebSocket auth |
All request bodies validated via Zod schemas
Path parameters (UUIDs, room codes) validated at controller level
Query parameters coerced and validated (invalid → 400)
File uploads via presigned S3 URLs (API never handles raw bytes)
Modified at 2026-03-12 05:26:10