Support cross-origin frontends alongside same-origin setups
- django-cors-headers with env-driven CORS_ALLOWED_ORIGINS, CORS_ALLOW_ALL_ORIGINS, CORS_ALLOW_CREDENTIALS and CSRF_TRUSTED_ORIGINS; same-origin traffic is unaffected and a disallowed origin gets no CORS headers. Token auth needs no cookies, so credentials stay off by default. - TRUST_PROXY_HEADERS=true lets a TLS-terminating proxy supply X-Forwarded-Proto/Host for correct absolute URLs. - API media URLs (raw/thumbnail/upload/similarity/staged previews) are now absolute, built from the request host, so <img>/<video>/fetch() keep working when the SPA is served from another origin. Signed URLs are still per-user; nothing is stored in the DB. - The SPA gains VITE_API_BASE (build-time, empty = same-origin) applied by a small apiUrl() helper used for XHR/fetch and the few URL fallbacks. Verified with a throwaway instance: preflight and GET responses carry the allowed origin, foreign origins get nothing, media GETs include CORS for cross-origin fetch(), and payload URLs use the request host (dev :8000 unchanged).
This commit is contained in:
@@ -103,10 +103,17 @@ class MediaItemSerializer(serializers.ModelSerializer):
|
||||
return f"J-{obj.id}"
|
||||
|
||||
def get_raw_url(self, obj):
|
||||
return signed_media_url(obj, self._request_user(), "raw")
|
||||
return signed_media_url(
|
||||
obj, self._request_user(), "raw", request=self.context.get("request")
|
||||
)
|
||||
|
||||
def get_thumbnail_url(self, obj):
|
||||
return signed_media_url(obj, self._request_user(), "thumbnail")
|
||||
return signed_media_url(
|
||||
obj,
|
||||
self._request_user(),
|
||||
"thumbnail",
|
||||
request=self.context.get("request"),
|
||||
)
|
||||
|
||||
def get_display_rating(self, obj):
|
||||
if obj.rating:
|
||||
@@ -184,7 +191,9 @@ class TempUploadSerializer(serializers.ModelSerializer):
|
||||
{"temp": str(obj.id), "user": user.id},
|
||||
salt=UPLOAD_FILE_SALT,
|
||||
)
|
||||
return f"/api/uploads/{obj.id}/file/?sig={signature}"
|
||||
url = f"/api/uploads/{obj.id}/file/?sig={signature}"
|
||||
request = self.context.get("request")
|
||||
return request.build_absolute_uri(url) if request is not None else url
|
||||
|
||||
def get_preview_url(self, obj):
|
||||
"""A URL an <img>/<video> tag can load: staged file or library item."""
|
||||
@@ -202,11 +211,9 @@ class TempUploadSerializer(serializers.ModelSerializer):
|
||||
and Path(location.rel_path).suffix.lower() in VIDEO_EXTENSIONS
|
||||
)
|
||||
action = "thumbnail" if is_video else "raw"
|
||||
signature = signing.dumps(
|
||||
{"item": item.id, "user": user.id, "action": action},
|
||||
salt=MEDIA_FILE_SALT,
|
||||
return signed_media_url(
|
||||
item, user, action, request=self.context.get("request")
|
||||
)
|
||||
return f"/api/files/J-{item.id}/{action}/?sig={signature}"
|
||||
|
||||
|
||||
class DownloadTaskSerializer(serializers.ModelSerializer):
|
||||
@@ -292,7 +299,9 @@ class SimilarityCheckSerializer(serializers.ModelSerializer):
|
||||
{"check": str(obj.id), "user": user.id},
|
||||
salt=UPLOAD_FILE_SALT,
|
||||
)
|
||||
return f"/api/similarity/{obj.id}/file/?sig={signature}"
|
||||
url = f"/api/similarity/{obj.id}/file/?sig={signature}"
|
||||
request = self.context.get("request")
|
||||
return request.build_absolute_uri(url) if request is not None else url
|
||||
|
||||
def get_expires_at(self, obj):
|
||||
ttl = int(getattr(settings, "SIMILARITY_TTL_MINUTES", 30))
|
||||
|
||||
Reference in New Issue
Block a user