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:
@@ -20,6 +20,38 @@ ALLOWED_HOSTS = [
|
||||
if host.strip()
|
||||
]
|
||||
|
||||
# Same-origin access always works; list extra frontend origins in
|
||||
# CORS_ALLOWED_ORIGINS (comma separated, e.g. https://j621.example.com).
|
||||
CORS_ALLOWED_ORIGINS = [
|
||||
origin.strip().rstrip("/")
|
||||
for origin in os.getenv("CORS_ALLOWED_ORIGINS", "").split(",")
|
||||
if origin.strip()
|
||||
]
|
||||
# Escape hatch for local experiments; never enable against a public server.
|
||||
CORS_ALLOW_ALL_ORIGINS = (
|
||||
os.getenv("CORS_ALLOW_ALL_ORIGINS", "false").lower() == "true"
|
||||
)
|
||||
# The SPA authenticates with an Authorization: Token header, not cookies, so
|
||||
# cross-origin requests do not need credentials. Enable this only if a
|
||||
# cross-origin client really relies on cookies (e.g. the Django admin).
|
||||
CORS_ALLOW_CREDENTIALS = (
|
||||
os.getenv("CORS_ALLOW_CREDENTIALS", "false").lower() == "true"
|
||||
)
|
||||
# Same list, for session/CSRF-protected endpoints (Django admin) reached from
|
||||
# another origin.
|
||||
CSRF_TRUSTED_ORIGINS = [
|
||||
origin.strip().rstrip("/")
|
||||
for origin in os.getenv("CSRF_TRUSTED_ORIGINS", "").split(",")
|
||||
if origin.strip()
|
||||
]
|
||||
|
||||
# Behind a TLS-terminating proxy the backend sees plain http; trust the
|
||||
# proxy's X-Forwarded-Proto/Host so absolute media URLs keep https and the
|
||||
# public hostname.
|
||||
if os.getenv("TRUST_PROXY_HEADERS", "false").lower() == "true":
|
||||
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
||||
USE_X_FORWARDED_HOST = True
|
||||
|
||||
|
||||
def _git_commit_hash():
|
||||
"""Short git hash of the running build, mirroring the original app."""
|
||||
@@ -50,6 +82,7 @@ INSTALLED_APPS = [
|
||||
"rest_framework",
|
||||
"rest_framework.authtoken",
|
||||
"django_filters",
|
||||
"corsheaders",
|
||||
"apps.accounts",
|
||||
"apps.library",
|
||||
"apps.follows",
|
||||
@@ -58,6 +91,8 @@ INSTALLED_APPS = [
|
||||
|
||||
MIDDLEWARE = [
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
# Must sit above CommonMiddleware so preflights are answered early.
|
||||
"corsheaders.middleware.CorsMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
|
||||
Reference in New Issue
Block a user