From f95de1482d814d69899848eb471a9753a38607c0 Mon Sep 17 00:00:00 2001 From: tegwick Date: Fri, 22 May 2026 02:02:42 +0200 Subject: [PATCH] Fix CSRF 403 on all POSTs behind traefik MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit prod.py never read the CSRF_TRUSTED_ORIGINS env var the deployment already injects, so Django's setting stayed empty. Behind traefik's TLS termination Django saw requests as HTTP and rejected the browser's https:// Origin on every POST with a CSRF failure (403) — forms could not be saved and the DB stayed empty. - Read CSRF_TRUSTED_ORIGINS from env (filtering empties). - Set SECURE_PROXY_SSL_HEADER so Django recognizes HTTPS via X-Forwarded-Proto. Co-Authored-By: Claude Opus 4.7 --- vergabe_teilnahme/settings/prod.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/vergabe_teilnahme/settings/prod.py b/vergabe_teilnahme/settings/prod.py index e020c08..cbd3983 100644 --- a/vergabe_teilnahme/settings/prod.py +++ b/vergabe_teilnahme/settings/prod.py @@ -5,6 +5,16 @@ from .base import * # noqa: F401, F403 DEBUG = False ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='').split(',') +# Behind traefik (TLS terminated at the proxy). Without these, Django sees the +# request as plain HTTP and rejects the browser's https:// Origin on every POST +# with a CSRF failure (403) — the request never reaches the view, so saves fail +# silently and the DB stays empty. The deployment already injects +# CSRF_TRUSTED_ORIGINS via env; this reads it. +CSRF_TRUSTED_ORIGINS = [ + o for o in config('CSRF_TRUSTED_ORIGINS', default='').split(',') if o +] +SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') + STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' SECURE_BROWSER_XSS_FILTER = True