Ermöglicht Apps außerhalb des Browsers (z.B. den TUI-Client), sich per Authorization: Bearer <token> zu authentifizieren statt per Session-Cookie. Die neue ApiTokenAuthMiddleware setzt request.user genau wie ein Login, wodurch alle bestehenden books/podcasts/radio-Endpunkte ohne Änderungen token-fähig werden. GET /api/sync/ liefert zusätzlich den kompletten Nutzerzustand (Bücher-Metadaten, Lesefortschritt, Notizen, Podcasts, Sender) in einem Request; Schreiben läuft weiter über die bestehenden Endpunkte, um deren Merge-Semantik (furthest-wins Progress, Notes-Upsert) wiederzuverwenden. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
25 lines
1.1 KiB
Python
25 lines
1.1 KiB
Python
from .models import ApiToken
|
|
|
|
|
|
class ApiTokenAuthMiddleware:
|
|
"""Authenticates requests carrying `Authorization: Bearer <token>` as the
|
|
owning user, for local/native clients that can't hold a Django session.
|
|
|
|
Must run after AuthenticationMiddleware. Leaves request.user untouched
|
|
(AnonymousUser) on missing/invalid tokens — existing view-level auth
|
|
checks (`_require_auth`, `login_required`, `is_authenticated`) already
|
|
handle that case with a 401/redirect, so there's nothing to do here."""
|
|
|
|
def __init__(self, get_response):
|
|
self.get_response = get_response
|
|
|
|
def __call__(self, request):
|
|
if not request.user.is_authenticated:
|
|
auth = request.META.get('HTTP_AUTHORIZATION', '')
|
|
if auth.startswith('Bearer '):
|
|
token = auth[len('Bearer '):].strip()
|
|
if token:
|
|
api_token = ApiToken.objects.select_related('user').filter(token=token).first()
|
|
if api_token is not None:
|
|
request.user = api_token.user
|
|
return self.get_response(request)
|