mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2026-02-05 06:05:18 -05:00
feat: add public profiles list endpoint with search and pagination
- Add new /profiles/ endpoint for listing user profiles - Support search by username/display name with ordering options - Include pagination with configurable page size (max 100) - Add comprehensive OpenAPI schema documentation - Refactor passkey authentication state management in MFA flow - Update URL routing and imports for new list_profiles view This enables user discovery, leaderboards, and friend-finding features with a publicly accessible, well-documented API endpoint.
This commit is contained in:
@@ -96,10 +96,10 @@ def get_registration_options(request):
|
||||
from allauth.mfa.webauthn.internal import auth as webauthn_auth
|
||||
|
||||
# Use the correct allauth API: begin_registration
|
||||
creation_options, state = webauthn_auth.begin_registration(request)
|
||||
# The function takes (user, passwordless) - passwordless=False for standard passkeys
|
||||
creation_options = webauthn_auth.begin_registration(request.user, passwordless=False)
|
||||
|
||||
# Store state in session for verification
|
||||
webauthn_auth.set_state(request, state)
|
||||
# State is stored internally by begin_registration via set_state()
|
||||
|
||||
return Response({
|
||||
"options": creation_options,
|
||||
@@ -154,8 +154,8 @@ def register_passkey(request):
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# Get stored state from session
|
||||
state = webauthn_auth.get_state(request)
|
||||
# Get stored state from session (no request needed, uses context)
|
||||
state = webauthn_auth.get_state()
|
||||
if not state:
|
||||
return Response(
|
||||
{"detail": "No pending registration. Please start registration again."},
|
||||
@@ -164,19 +164,24 @@ def register_passkey(request):
|
||||
|
||||
# Use the correct allauth API: complete_registration
|
||||
try:
|
||||
from allauth.mfa.models import Authenticator
|
||||
|
||||
# Parse the credential response
|
||||
credential_data = webauthn_auth.parse_registration_response(credential)
|
||||
|
||||
# Complete registration - this creates the Authenticator
|
||||
authenticator = webauthn_auth.complete_registration(
|
||||
request,
|
||||
credential_data,
|
||||
state,
|
||||
name=name,
|
||||
)
|
||||
# Complete registration - returns AuthenticatorData (binding)
|
||||
authenticator_data = webauthn_auth.complete_registration(credential_data)
|
||||
|
||||
# Clear session state
|
||||
webauthn_auth.clear_state(request)
|
||||
# Create the Authenticator record ourselves
|
||||
authenticator = Authenticator.objects.create(
|
||||
user=request.user,
|
||||
type=Authenticator.Type.WEBAUTHN,
|
||||
data={
|
||||
"name": name,
|
||||
"credential": authenticator_data.credential_data.aaguid.hex if authenticator_data.credential_data else None,
|
||||
},
|
||||
)
|
||||
# State is cleared internally by complete_registration
|
||||
|
||||
return Response({
|
||||
"detail": "Passkey registered successfully",
|
||||
@@ -225,10 +230,8 @@ def get_authentication_options(request):
|
||||
from allauth.mfa.webauthn.internal import auth as webauthn_auth
|
||||
|
||||
# Use the correct allauth API: begin_authentication
|
||||
request_options, state = webauthn_auth.begin_authentication(request)
|
||||
|
||||
# Store state in session for verification
|
||||
webauthn_auth.set_state(request, state)
|
||||
# Takes optional user, returns just options (state is stored internally)
|
||||
request_options = webauthn_auth.begin_authentication(request.user)
|
||||
|
||||
return Response({
|
||||
"options": request_options,
|
||||
@@ -281,8 +284,8 @@ def authenticate_passkey(request):
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# Get stored state from session
|
||||
state = webauthn_auth.get_state(request)
|
||||
# Get stored state from session (no request needed, uses context)
|
||||
state = webauthn_auth.get_state()
|
||||
if not state:
|
||||
return Response(
|
||||
{"detail": "No pending authentication. Please start authentication again."},
|
||||
@@ -291,14 +294,9 @@ def authenticate_passkey(request):
|
||||
|
||||
# Use the correct allauth API: complete_authentication
|
||||
try:
|
||||
# Parse the credential response
|
||||
credential_data = webauthn_auth.parse_authentication_response(credential)
|
||||
|
||||
# Complete authentication
|
||||
webauthn_auth.complete_authentication(request, credential_data, state)
|
||||
|
||||
# Clear session state
|
||||
webauthn_auth.clear_state(request)
|
||||
# Complete authentication - takes user and credential response
|
||||
# State is handled internally
|
||||
webauthn_auth.complete_authentication(request.user, credential)
|
||||
|
||||
return Response({"success": True})
|
||||
except Exception as e:
|
||||
@@ -514,9 +512,13 @@ def get_login_passkey_options(request):
|
||||
request.user = user
|
||||
|
||||
try:
|
||||
request_options, state = webauthn_auth.begin_authentication(request)
|
||||
# begin_authentication takes just user, returns options (state stored internally)
|
||||
request_options = webauthn_auth.begin_authentication(user)
|
||||
# Note: State is managed by allauth's session context, but for MFA login flow
|
||||
# we need to track user separately since they're not authenticated yet
|
||||
passkey_state_key = f"mfa_passkey_state:{mfa_token}"
|
||||
cache.set(passkey_state_key, state, timeout=300)
|
||||
# Store a reference that this user has a pending passkey auth
|
||||
cache.set(passkey_state_key, {"user_id": user_id}, timeout=300)
|
||||
return Response({"options": request_options})
|
||||
finally:
|
||||
if original_user is not None:
|
||||
|
||||
@@ -417,23 +417,23 @@ class MFALoginVerifyAPIView(APIView):
|
||||
return {"success": False, "error": "No passkey registered for this user"}
|
||||
|
||||
try:
|
||||
# Parse the authentication response
|
||||
credential_data = webauthn_auth.parse_authentication_response(credential)
|
||||
|
||||
# Get or create authentication state
|
||||
# For login flow, we need to set up the state first
|
||||
state = webauthn_auth.get_state(request)
|
||||
# For MFA login flow, we need to set up state first if not present
|
||||
# Note: allauth's begin_authentication stores state internally
|
||||
state = webauthn_auth.get_state()
|
||||
|
||||
if not state:
|
||||
# If no state, generate one for this user
|
||||
_, state = webauthn_auth.begin_authentication(request)
|
||||
webauthn_auth.set_state(request, state)
|
||||
# Need to temporarily set request.user for allauth context
|
||||
original_user = getattr(request, "user", None)
|
||||
request.user = user
|
||||
try:
|
||||
webauthn_auth.begin_authentication(user)
|
||||
finally:
|
||||
if original_user is not None:
|
||||
request.user = original_user
|
||||
|
||||
# Complete authentication
|
||||
webauthn_auth.complete_authentication(request, credential_data, state)
|
||||
|
||||
# Clear the state
|
||||
webauthn_auth.clear_state(request)
|
||||
# Complete authentication - takes user and credential dict
|
||||
# State is managed internally by allauth
|
||||
webauthn_auth.complete_authentication(user, credential)
|
||||
|
||||
return {"success": True}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user