Auth and Security
Security boundaries are enforced on the backend. The frontend can hide controls for usability, but hidden UI is never authorization.
Authentication
Auth routes live in server/app/routers/auth.py. Passwords are hashed with
bcrypt. Login/signup return a token for API clients and set an httpOnly
tome_token cookie for the web app.
The backend also supports:
- Email verification.
- Password reset tokens.
- Logout with token-version revocation.
- Discord OAuth when configured.
Frontend auth state lives in web/src/lib/auth.tsx.
Authorization Layers
| Layer | Meaning | Code |
|---|---|---|
| Request auth | User is signed in. | require_auth in server/app/deps.py |
| Platform admin | User can access /api/admin/*. | require_admin |
| Campaign member | User belongs to the campaign. | require_member |
| Campaign DM | User can perform DM-only actions. | require_dm |
| AI access | User can use AI features. | require_ai_access |
Platform admin is User.isAdmin. Campaign role is a Membership.role. AI
access is separate: effective access is User.aiAccess OR User.isAdmin.
API Safety Rules
- Validate every request body with Pydantic.
- Scope every query by current user, campaign, or admin role.
- Never return private storage paths directly.
- Never serve admin core-rule files from public
/uploads. - Use standard error helpers so clients get predictable envelopes.
- Keep secrets in environment variables.
Public and Private Files
Public uploads are for user-visible images and are served from /uploads.
Core-rule files live under private server/storage/ and are reachable only
through admin download routes.
Rate Limits and Headers
Rate limiting helpers live in server/app/lib/rate_limit.py. Security headers
are added by server/app/security_headers.py. Public invite preview and auth
flows have additional hardening paths.