fix: Hinter einem Reverse Proxy zeigte die SSO-Rueckleitung nach innen

Die Adresse, an die der Anmeldedienst zurueckschickt, wurde aus der Anfrage
abgeleitet. Hinter einem Proxy ist das die INTERNE – http://10.0.0.5:8071/… –,
denn uvicorn lief ohne --proxy-headers. Der Anmeldedienst haette die
Rueckleitung abgewiesen, mit einer Meldung, die nicht sagt, woran es liegt.

APP_PUBLIC_BASE_URL sagt es ihm jetzt ausdruecklich. Leer bleiben darf sie
weiterhin: ohne Proxy und in der Entwicklung genuegt die Anfrage.

Die Referenz-Unit bekommt zusaetzlich --proxy-headers. Das hilft neuen
Installationen; bestehende ruehrt update.sh nicht an, deshalb ist die
Einstellung der verlaessliche Weg.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-09-09 19:39:51 +02:00
co-authored by Claude Opus 5
parent 7690718731
commit bd2c623136
5 changed files with 71 additions and 6 deletions
+14
View File
@@ -38,6 +38,20 @@ class Settings(BaseSettings):
# Beim Start die aktuellen EZB-Kurse nachladen (benötigt Internet).
ecb_fetch_on_start: bool = True
# --- Oeffentliche Adresse ------------------------------------------------
# Unter welcher Adresse Certain von aussen erreichbar ist, z. B.
# https://certain.example.com – OHNE Pfad am Ende.
#
# Gebraucht fuer die Rueckleit-Adresse der SSO-Anmeldung. Die liesse sich
# zwar aus der Anfrage ableiten, aber hinter einem Reverse Proxy kaeme
# dabei die INTERNE Adresse heraus (http://10.0.0.5:8071/...), und der
# Anmeldedienst weist sie ab – mit einer Fehlermeldung, die nicht sagt,
# woran es liegt. Wer sie hier hinschreibt, hat das Problem nicht.
#
# Leer = aus der Anfrage ableiten. Das genuegt ohne Proxy und in der
# Entwicklung.
public_base_url: str = ""
# --- CORS ----------------------------------------------------------------
# Leer = kein Cross-Origin (gleiche Herkunft, da FastAPI das SPA selbst
# ausliefert). Kommagetrennte Liste oder "*" nur bei Bedarf.
+13 -5
View File
@@ -47,12 +47,20 @@ def _now() -> datetime:
def _redirect_uri(request: Request, provider: str) -> str:
"""Die Adresse, an die der Anbieter zurueckschickt.
Aus der Anfrage abgeleitet, damit Entwicklung (http://localhost:5173) und
Betrieb (https://…) ohne zweite Einstellung funktionieren. Sie muss beim
Anbieter EXAKT so eingetragen sein – auch beim Einloesen des Codes wird
sie noch einmal mitgeschickt und dort verglichen.
Sie muss beim Anbieter EXAKT so eingetragen sein – beim Einloesen des Codes
wird sie noch einmal mitgeschickt und dort verglichen. Ein Zeichen
Unterschied, und die Anmeldung scheitert.
Steht ``APP_PUBLIC_BASE_URL``, gilt die. Sonst wird sie aus der Anfrage
abgeleitet – das genuegt in der Entwicklung, geht hinter einem Reverse
Proxy aber schief: Dort kaeme die INTERNE Adresse heraus
(http://10.0.0.5:8071/...), weil uvicorn die Weiterleitungs-Kopfzeilen
nicht auswertet. Genau deshalb gibt es die Einstellung.
"""
return str(request.url_for("oidc_callback", provider=provider))
pfad = request.url_for("oidc_callback", provider=provider).path
basis = get_settings().public_base_url.strip().rstrip("/")
return f"{basis}{pfad}" if basis else str(request.url_for(
"oidc_callback", provider=provider))
def _fehlerseite(weiter: str, meldung: str) -> RedirectResponse:
+34
View File
@@ -431,3 +431,37 @@ def test_konto_ohne_passwort_faellt_beim_passwort_login_nicht_durch(client, anbi
eintrag = s.query(UserIdentity).filter(UserIdentity.subject == sub).first()
s.delete(s.get(User, eintrag.user_id))
s.commit()
# --- Rueckleit-Adresse hinter einem Proxy ------------------------------------
def test_oeffentliche_adresse_schlaegt_die_der_anfrage(client, anbieter, monkeypatch):
"""Hinter einem Reverse Proxy kaeme aus der Anfrage die INTERNE Adresse –
der Anmeldedienst weist sie ab, und die Fehlermeldung sagt nicht, warum."""
from urllib.parse import parse_qs, urlparse
from app.config import get_settings
get_settings.cache_clear()
monkeypatch.setenv("APP_PUBLIC_BASE_URL", "https://certain.example.com")
try:
r = client.get("/api/auth/oidc/authentik/start", follow_redirects=False)
frage = parse_qs(urlparse(r.headers["location"]).query)
assert frage["redirect_uri"] == [
"https://certain.example.com/api/auth/oidc/authentik/callback"
], frage["redirect_uri"]
finally:
get_settings.cache_clear()
def test_ohne_einstellung_kommt_die_adresse_aus_der_anfrage(client, anbieter):
"""Ohne Proxy und in der Entwicklung soll nichts eingestellt werden muessen."""
from urllib.parse import parse_qs, urlparse
from app.config import get_settings
get_settings.cache_clear()
r = client.get("/api/auth/oidc/authentik/start", follow_redirects=False)
frage = parse_qs(urlparse(r.headers["location"]).query)
assert frage["redirect_uri"][0].endswith("/api/auth/oidc/authentik/callback")