Based on the git diff provided, here's a concise and descriptive commit message:

feat: add security event taxonomy and optimize park queryset

- Add comprehensive security_event_types ChoiceGroup with categories for authentication, MFA, password, account, session, and API key events
- Include severity levels, icons, and CSS classes for each event type
- Fix park queryset optimization by using select_related for OneToOne location relationship
- Remove location property fields (latitude/longitude) from values() call as they are not actual DB columns
- Add proper location fields (city, state, country) to values() for map display

This change enhances security event tracking capabilities and resolves a queryset optimization issue where property decorators were incorrectly used in values() queries.
This commit is contained in:
pacnpal
2026-01-10 16:41:31 -05:00
parent 96df23242e
commit 2b66814d82
26 changed files with 2055 additions and 112 deletions

View File

@@ -608,28 +608,118 @@ After authentication completes with JWT enabled:
Authorization: Bearer <access-token>
```
---
## Current ThrillWiki Implementation Summary
ThrillWiki already has these allauth features configured:
ThrillWiki uses a hybrid authentication system with django-allauth for MFA and social auth, and SimpleJWT for API tokens.
### Backend Configuration
| Feature | Status | Notes |
|---------|--------|-------|
| Password Auth | ✅ Configured | Email + username login |
| Password Auth | ✅ Active | Email + username login |
| Email Verification | ✅ Mandatory | With resend support |
| TOTP MFA | ✅ Configured | 6-digit codes, 30s period |
| WebAuthn/Passkeys | ✅ Configured | Passkey login enabled |
| Google OAuth | ✅ Configured | Needs admin SocialApp |
| Discord OAuth | ✅ Configured | Needs admin SocialApp |
| TOTP MFA | ✅ Active | 6-digit codes, 30s period |
| WebAuthn/Passkeys | ✅ Active | Passkey login enabled, counts as MFA |
| Google OAuth | ✅ Configured | Requires admin SocialApp setup |
| Discord OAuth | ✅ Configured | Requires admin SocialApp setup |
| Magic Link | ✅ Configured | 5-minute timeout |
| JWT Tokens | ❌ Not configured | Using SimpleJWT instead |
| JWT Tokens | ✅ SimpleJWT | 15min access, 7 day refresh |
### Recommendation
### Frontend MFA Integration (Updated 2026-01-10)
To use allauth's native JWT support instead of SimpleJWT:
The frontend recognizes both TOTP and Passkeys as valid MFA factors:
1. Add `"allauth.headless"` to INSTALLED_APPS
2. Configure `HEADLESS_TOKEN_STRATEGY` and JWT settings
3. Replace `rest_framework_simplejwt` authentication with `JWTTokenAuthentication`
4. Add `/_allauth/` URL routes
```typescript
// authService.ts - getEnrolledFactors()
// Checks both Supabase TOTP AND Django passkeys
const mfaStatus = await djangoClient.rpc('get_mfa_status', {});
if (statusData.passkey_enabled && statusData.passkey_count > 0) {
factors.push({ id: 'passkey', factor_type: 'webauthn', ... });
}
```
### Admin Panel MFA Requirements
Admins and moderators must have MFA enabled to access protected routes:
1. `useAdminGuard()` hook checks MFA enrollment via `useRequireMFA()`
2. `getEnrolledFactors()` queries Django's `get_mfa_status` endpoint
3. Backend returns `has_second_factor: true` if TOTP or Passkey is enabled
4. Users with only passkeys (no TOTP) now pass the MFA requirement
### API Endpoints Reference
#### Authentication
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/auth/login/` | POST | Password login |
| `/api/v1/auth/login/mfa-verify/` | POST | Complete MFA (TOTP or Passkey) |
| `/api/v1/auth/signup/` | POST | Register new account |
| `/api/v1/auth/logout/` | POST | Logout, blacklist tokens |
| `/api/v1/auth/token/refresh/` | POST | Refresh JWT access token |
#### MFA (TOTP)
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/auth/mfa/status/` | GET | Get MFA status (TOTP + Passkey) |
| `/api/v1/auth/mfa/totp/setup/` | POST | Start TOTP enrollment |
| `/api/v1/auth/mfa/totp/activate/` | POST | Activate with 6-digit code |
| `/api/v1/auth/mfa/totp/deactivate/` | POST | Remove TOTP (requires password) |
#### Passkeys (WebAuthn)
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/auth/passkey/status/` | GET | List registered passkeys |
| `/api/v1/auth/passkey/registration-options/` | GET | Get WebAuthn creation options |
| `/api/v1/auth/passkey/register/` | POST | Complete passkey registration |
| `/api/v1/auth/passkey/login-options/` | POST | Get auth options (uses mfa_token) |
| `/api/v1/auth/passkey/{id}/` | DELETE | Remove passkey |
#### Social Authentication
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/auth/social/providers/` | GET | List configured providers |
| `/api/v1/auth/social/connect/{provider}/` | POST | Start OAuth flow |
| `/api/v1/auth/social/disconnect/{provider}/` | POST | Unlink provider |
### Login Flow with MFA
```
1. POST /api/v1/auth/login/ {username, password}
└── If MFA enabled: Returns {mfa_required: true, mfa_token, mfa_types: ["totp", "webauthn"]}
2a. TOTP Verification:
POST /api/v1/auth/login/mfa-verify/ {mfa_token, code: "123456"}
2b. Passkey Verification:
POST /api/v1/auth/passkey/login-options/ {mfa_token} ← Get challenge
Browser: navigator.credentials.get() ← User authenticates
POST /api/v1/auth/login/mfa-verify/ {mfa_token, credential: {...}}
3. Returns: {access, refresh, user, message: "Login successful"}
```
### Frontend Components
| Component | Purpose |
|-----------|---------|
| `MFAChallenge.tsx` | TOTP code entry during login |
| `MFAEnrollmentRequired.tsx` | Prompts admin/mod to set up MFA |
| `MFAGuard.tsx` | Wraps routes requiring MFA |
| `useRequireMFA` | Hook checking MFA enrollment |
| `useAdminGuard` | Combines auth + role + MFA checks |
### Admin MFA Requirements
Moderators, admins, and superusers **must have MFA enrolled** to access admin pages.
The system uses **enrollment-based verification** rather than per-session AAL2 tokens:
- MFA verification happens at login time via the `mfa_token` flow
- Django-allauth doesn't embed AAL claims in JWT tokens
- The frontend checks if the user has TOTP or passkey enrolled
- Mid-session MFA step-up is not supported (user must re-login)
This means:
- `useRequireMFA` returns `hasMFA: true` if user has any factor enrolled
- `useAdminGuard` blocks access if `needsEnrollment` is true
- Users prompted to enroll MFA on their first admin page visit