Fix staged upload previews and allow WebP
- Staged files are now served through a signed URL (Django signing, 24h) so <img>/<video> tags can load previews without an Authorization header; the file endpoint accepts header auth or a valid signature, rejects tampered signatures, and still scopes access to the owner - Serializer responses now carry the request context so URLs are signed per user - Add .webp to the allowed extensions (backend + upload hint)
This commit is contained in:
@@ -14,10 +14,13 @@ import shutil
|
||||
from pathlib import Path
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core import signing
|
||||
from django.http import Http404
|
||||
from rest_framework import mixins, status, viewsets
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.parsers import FormParser, JSONParser, MultiPartParser
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.response import Response
|
||||
|
||||
from . import services
|
||||
@@ -116,12 +119,42 @@ class TempUploadViewSet(
|
||||
temp.file.delete(save=False)
|
||||
temp.save()
|
||||
return Response(
|
||||
TempUploadSerializer(temp).data, status=status.HTTP_201_CREATED
|
||||
self.get_serializer(temp).data, status=status.HTTP_201_CREATED
|
||||
)
|
||||
|
||||
@action(detail=True, methods=["get"])
|
||||
@action(detail=True, methods=["get", "head"], permission_classes=[AllowAny])
|
||||
def file(self, request, pk=None):
|
||||
temp = self.get_object()
|
||||
"""Serve the staged file; accepts a signed URL for media tags."""
|
||||
user = request.user if request.user.is_authenticated else None
|
||||
if user is None:
|
||||
signature = request.query_params.get("sig")
|
||||
if signature:
|
||||
try:
|
||||
payload = signing.loads(
|
||||
signature,
|
||||
salt=services.UPLOAD_FILE_SALT,
|
||||
max_age=86400,
|
||||
)
|
||||
except signing.BadSignature:
|
||||
payload = None
|
||||
if payload and str(payload.get("temp")) == str(pk):
|
||||
user = (
|
||||
get_user_model()
|
||||
.objects.filter(pk=payload.get("user"))
|
||||
.first()
|
||||
)
|
||||
if user is None or not user.can_upload:
|
||||
return Response(
|
||||
{"detail": "Authentication required."},
|
||||
status=status.HTTP_401_UNAUTHORIZED,
|
||||
)
|
||||
|
||||
temp = TempUpload.objects.filter(pk=pk).first()
|
||||
is_owner = temp is not None and temp.user_id == user.id
|
||||
if temp is None or not (
|
||||
is_owner or user.is_staff or user.is_superuser
|
||||
):
|
||||
raise Http404
|
||||
if not temp.file:
|
||||
raise Http404
|
||||
return services.serve_file(request, temp.file.path)
|
||||
@@ -139,7 +172,7 @@ class TempUploadViewSet(
|
||||
if temp.status == TempUpload.STATUS_PENDING and temp.iqdb_data:
|
||||
temp.status = TempUpload.STATUS_VISUAL_MATCH
|
||||
temp.save(update_fields=["iqdb_data", "status", "updated_at"])
|
||||
return Response(TempUploadSerializer(temp).data)
|
||||
return Response(self.get_serializer(temp).data)
|
||||
|
||||
@action(detail=True, methods=["post"])
|
||||
def resolve(self, request, pk=None):
|
||||
@@ -204,7 +237,7 @@ class TempUploadViewSet(
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
temp.refresh_from_db()
|
||||
return Response(TempUploadSerializer(temp).data)
|
||||
return Response(self.get_serializer(temp).data)
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
if instance.file:
|
||||
|
||||
Reference in New Issue
Block a user