working base site setup with gatekeeping working
This commit is contained in:
@@ -64,26 +64,42 @@ def _is_api_request(request):
|
||||
return False
|
||||
|
||||
|
||||
def _get_bearer_token(request):
|
||||
def _get_credentials(request):
|
||||
"""Extract (username, token) from 'Authorization: Bearer <username> <token>'."""
|
||||
auth = request.headers.get('Authorization', '')
|
||||
if auth.lower().startswith('bearer '):
|
||||
return auth.split(' ', 1)[1].strip()
|
||||
explicit = request.headers.get('X-Auth-Token')
|
||||
return explicit or None
|
||||
if not auth.lower().startswith('bearer '):
|
||||
return None, None
|
||||
parts = auth.split(' ', 2) # ['Bearer', '<username>', '<token>']
|
||||
if len(parts) < 3:
|
||||
return None, None
|
||||
return parts[1].strip(), parts[2].strip()
|
||||
|
||||
|
||||
def _authenticate_token(request, token):
|
||||
def _authenticate_token(request, username, token):
|
||||
import hmac
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.utils import timezone
|
||||
|
||||
from profiles.models import ApiToken
|
||||
|
||||
if not token:
|
||||
if not username or not token:
|
||||
return False
|
||||
|
||||
User = get_user_model()
|
||||
# Resolve the user case-insensitively; prefer an exact-case match if it
|
||||
# exists to avoid ambiguity.
|
||||
user = (
|
||||
User.objects.filter(username=username).first()
|
||||
or User.objects.filter(username__iexact=username).first()
|
||||
)
|
||||
if user is None:
|
||||
return False
|
||||
|
||||
prefix = token[:8]
|
||||
# Matches on the prefix keyed index, then constant-time compares.
|
||||
for candidate in ApiToken.objects.filter(key_prefix=prefix):
|
||||
# Scope the lookup to the claimed user so a token never authenticates as
|
||||
# anyone but its owner (prevents cross-authentication).
|
||||
for candidate in ApiToken.objects.filter(user=user, key_prefix=prefix):
|
||||
if hmac.compare_digest(candidate.token or '', token):
|
||||
candidate.last_used = timezone.now()
|
||||
candidate.save(update_fields=['last_used'])
|
||||
@@ -117,11 +133,15 @@ class GateMiddleware:
|
||||
if _is_whitelisted(request):
|
||||
return self.get_response(request)
|
||||
|
||||
# API/media requests are allowed a bearer token to bypass the gate
|
||||
# (non-browser downloaders such as the Fabric mod).
|
||||
# API/media requests are allowed a bearer credential to bypass the
|
||||
# gate (non-browser downloaders such as the Fabric mod). Credentials
|
||||
# are 'Authorization: Bearer <username> <token>'. Token auth is
|
||||
# CSRF-safe (header-based, not cookie-based), so mark the request as
|
||||
# CSRF-processed to let it reach the view.
|
||||
if _is_token_request(request):
|
||||
token = _get_bearer_token(request)
|
||||
if _authenticate_token(request, token):
|
||||
username, token = _get_credentials(request)
|
||||
if _authenticate_token(request, username, token):
|
||||
request.csrf_processing_done = True
|
||||
return self.get_response(request)
|
||||
# fall through: reject with 401 for API/media.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user