Files
thrillwiki_django_no_react/backend/apps/api/v1/auth/jwt.py
pacnpal 2b66814d82 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.
2026-01-10 16:41:31 -05:00

97 lines
2.6 KiB
Python

"""
Custom JWT Token Generation for ThrillWiki
This module provides custom JWT token generation that includes authentication
method claims for enhanced MFA satisfaction logic.
Claims added:
- auth_method: How the user authenticated (password, passkey, totp, google, discord)
- mfa_verified: Whether MFA was verified during this login
- provider_mfa: Whether the OAuth provider (Discord) has MFA enabled
"""
from typing import Literal, TypedDict
from rest_framework_simplejwt.tokens import RefreshToken
# Type definitions for auth methods
AuthMethod = Literal["password", "passkey", "totp", "google", "discord"]
class TokenClaims(TypedDict, total=False):
"""Type definition for custom JWT claims."""
auth_method: AuthMethod
mfa_verified: bool
provider_mfa: bool
def create_tokens_for_user(
user,
auth_method: AuthMethod = "password",
mfa_verified: bool = False,
provider_mfa: bool = False,
) -> dict[str, str]:
"""
Generate JWT tokens with custom authentication claims.
Args:
user: The Django user object
auth_method: How the user authenticated
mfa_verified: True if MFA (TOTP/passkey) was verified at login
provider_mfa: True if OAuth provider (Discord) has MFA enabled
Returns:
Dictionary with 'access' and 'refresh' token strings
"""
refresh = RefreshToken.for_user(user)
# Add custom claims to both refresh and access tokens
refresh["auth_method"] = auth_method
refresh["mfa_verified"] = mfa_verified
refresh["provider_mfa"] = provider_mfa
access = refresh.access_token
return {
"access": str(access),
"refresh": str(refresh),
}
def get_auth_method_for_provider(provider: str) -> AuthMethod:
"""
Map OAuth provider name to AuthMethod type.
Args:
provider: The provider name (e.g., 'google', 'discord')
Returns:
The corresponding AuthMethod
"""
provider_map: dict[str, AuthMethod] = {
"google": "google",
"discord": "discord",
}
return provider_map.get(provider, "password")
def get_provider_mfa_status(provider: str, extra_data: dict) -> bool:
"""
Extract MFA status from OAuth provider extra_data.
Only Discord exposes mfa_enabled. Google does not share this info.
Args:
provider: The OAuth provider name
extra_data: The extra_data dict from SocialAccount
Returns:
True if provider has MFA enabled, False otherwise
"""
if provider == "discord":
return extra_data.get("mfa_enabled", False)
# Google and other providers don't expose MFA status
return False