77"""
88
99import logging
10+ from typing import TYPE_CHECKING , Optional
1011from urllib .parse import urlencode
1112
1213from django .conf import settings
1314from django .contrib import admin , messages
1415from django .contrib .auth import authenticate , login , logout
15- from django .http import HttpResponse , HttpResponseNotAllowed , JsonResponse
16+ from django .http import (
17+ HttpRequest ,
18+ HttpResponse ,
19+ HttpResponseBase ,
20+ HttpResponseNotAllowed ,
21+ JsonResponse ,
22+ )
23+
24+ if TYPE_CHECKING :
25+ from opencontractserver .users .models import User
1626from django .shortcuts import redirect , render
1727from django .urls import reverse
1828from django .utils .decorators import method_decorator
3545ADMIN_LOGIN_PAGE_RATE = RateLimits .ADMIN_LOGIN_PAGE
3646
3747
38- def _get_login_url ():
48+ def _get_login_url () -> str :
3949 """Get the admin login URL using reverse() for proper URL resolution."""
4050 return reverse ("admin_auth0_login" )
4151
4252
43- def _get_admin_index_url ():
53+ def _get_admin_index_url () -> str :
4454 """Get the admin index URL using reverse() for proper URL resolution."""
4555 return reverse ("admin:index" )
4656
4757
48- def _get_next_url_from_request (request ) :
58+ def _get_next_url_from_request (request : HttpRequest ) -> Optional [ str ] :
4959 """
5060 Extract the 'next' parameter from request, checking POST first then GET.
5161
@@ -60,7 +70,11 @@ def _get_next_url_from_request(request):
6070 return request .POST .get ("next" ) or request .GET .get ("next" )
6171
6272
63- def _get_safe_redirect_url (request , url = None , default = None ):
73+ def _get_safe_redirect_url (
74+ request : HttpRequest ,
75+ url : Optional [str ] = None ,
76+ default : Optional [str ] = None ,
77+ ) -> str :
6478 """
6579 Validate and return a safe redirect URL.
6680
@@ -103,7 +117,7 @@ def _get_safe_redirect_url(request, url=None, default=None):
103117 return default
104118
105119
106- def _get_safe_logout_return_url (request ) :
120+ def _get_safe_logout_return_url (request : HttpRequest ) -> str :
107121 """
108122 Get a safe return URL for Auth0 logout.
109123
@@ -163,7 +177,7 @@ class Auth0AdminLoginView(View):
163177
164178 @method_decorator (csrf_protect )
165179 @method_decorator (view_ratelimit (rate = ADMIN_LOGIN_PAGE_RATE , block = False ))
166- def get (self , request ) :
180+ def get (self , request : HttpRequest ) -> HttpResponseBase :
167181 """Display the appropriate login form."""
168182 if getattr (request , "limited" , False ):
169183 logger .warning ("Rate limit exceeded for admin login page GET" )
@@ -203,7 +217,7 @@ def get(self, request):
203217
204218 @method_decorator (csrf_protect )
205219 @method_decorator (view_ratelimit (rate = ADMIN_LOGIN_RATE , block = False ))
206- def post (self , request ) :
220+ def post (self , request : HttpRequest ) -> HttpResponseBase :
207221 """Handle token-based login via POST or password authentication."""
208222 if getattr (request , "limited" , False ):
209223 logger .warning ("Rate limit exceeded for admin login POST" )
@@ -236,7 +250,7 @@ def post(self, request):
236250 return redirect (_get_login_url ())
237251
238252 @staticmethod
239- def _get_csp_nonce (request ) :
253+ def _get_csp_nonce (request : HttpRequest ) -> str :
240254 """Return the CSP nonce from the request, warning if it is missing.
241255
242256 An empty nonce would produce an invalid CSP directive that silently
@@ -252,12 +266,14 @@ def _get_csp_nonce(request):
252266 return ""
253267 return nonce
254268
255- def _authenticate_with_token (self , request , token ):
269+ def _authenticate_with_token (
270+ self , request : HttpRequest , token : str
271+ ) -> HttpResponseBase :
256272 """Authenticate user with Auth0 JWT token."""
257273 from config .jwt_utils import get_user_from_jwt_token
258274
259275 try :
260- user = get_user_from_jwt_token (token )
276+ user : Optional [ "User" ] = get_user_from_jwt_token (token )
261277
262278 if user and user .is_active :
263279 # Sync admin claims from token (only during admin login, not API requests)
@@ -282,12 +298,12 @@ def _authenticate_with_token(self, request, token):
282298 )
283299 # Validate redirect URL to prevent open redirect attacks
284300 next_url = _get_safe_redirect_url (request )
285- logger .info ("Admin login successful for user ID %s" , user .id )
301+ logger .info ("Admin login successful for user ID %s" , user .pk )
286302 return redirect (next_url )
287303 else :
288304 logger .warning (
289305 "User ID %s denied admin access" ,
290- user .id if user else "unknown" ,
306+ user .pk if user else "unknown" ,
291307 )
292308 messages .error (
293309 request , "You do not have permission to access the admin."
@@ -299,7 +315,7 @@ def _authenticate_with_token(self, request, token):
299315 messages .error (request , "Authentication failed. Please try again." )
300316 return redirect (_get_login_url ())
301317
302- def _sync_admin_claims (self , user , token ) :
318+ def _sync_admin_claims (self , user : "User" , token : str ) -> bool :
303319 """
304320 Sync admin claims from Auth0 token to user model.
305321
@@ -324,7 +340,7 @@ def _sync_admin_claims(self, user, token):
324340 return sync_admin_claims_from_payload (user , payload )
325341 except Exception as e :
326342 # Log but don't fail authentication - claim sync is secondary
327- logger .warning ("Failed to sync admin claims for user ID %s: %s" , user .id , e )
343+ logger .warning ("Failed to sync admin claims for user ID %s: %s" , user .pk , e )
328344 return False
329345
330346
@@ -337,7 +353,7 @@ class Auth0AdminLogoutView(View):
337353 """
338354
339355 @method_decorator (csrf_protect )
340- def post (self , request ) :
356+ def post (self , request : HttpRequest ) -> HttpResponseBase :
341357 """Log out the user and redirect appropriately."""
342358 logout (request )
343359
@@ -355,7 +371,7 @@ def post(self, request):
355371
356372 return redirect (_get_admin_index_url ())
357373
358- def get (self , request ) :
374+ def get (self , request : HttpRequest ) -> HttpResponse :
359375 """
360376 Reject GET requests for logout.
361377
0 commit comments