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:
co-authored by
Claude Opus 5
parent
7690718731
commit
bd2c623136
@@ -38,6 +38,20 @@ class Settings(BaseSettings):
|
|||||||
# Beim Start die aktuellen EZB-Kurse nachladen (benötigt Internet).
|
# Beim Start die aktuellen EZB-Kurse nachladen (benötigt Internet).
|
||||||
ecb_fetch_on_start: bool = True
|
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 ----------------------------------------------------------------
|
# --- CORS ----------------------------------------------------------------
|
||||||
# Leer = kein Cross-Origin (gleiche Herkunft, da FastAPI das SPA selbst
|
# Leer = kein Cross-Origin (gleiche Herkunft, da FastAPI das SPA selbst
|
||||||
# ausliefert). Kommagetrennte Liste oder "*" nur bei Bedarf.
|
# ausliefert). Kommagetrennte Liste oder "*" nur bei Bedarf.
|
||||||
|
|||||||
@@ -47,12 +47,20 @@ def _now() -> datetime:
|
|||||||
def _redirect_uri(request: Request, provider: str) -> str:
|
def _redirect_uri(request: Request, provider: str) -> str:
|
||||||
"""Die Adresse, an die der Anbieter zurueckschickt.
|
"""Die Adresse, an die der Anbieter zurueckschickt.
|
||||||
|
|
||||||
Aus der Anfrage abgeleitet, damit Entwicklung (http://localhost:5173) und
|
Sie muss beim Anbieter EXAKT so eingetragen sein – beim Einloesen des Codes
|
||||||
Betrieb (https://…) ohne zweite Einstellung funktionieren. Sie muss beim
|
wird sie noch einmal mitgeschickt und dort verglichen. Ein Zeichen
|
||||||
Anbieter EXAKT so eingetragen sein – auch beim Einloesen des Codes wird
|
Unterschied, und die Anmeldung scheitert.
|
||||||
sie noch einmal mitgeschickt und dort verglichen.
|
|
||||||
|
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:
|
def _fehlerseite(weiter: str, meldung: str) -> RedirectResponse:
|
||||||
|
|||||||
@@ -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()
|
eintrag = s.query(UserIdentity).filter(UserIdentity.subject == sub).first()
|
||||||
s.delete(s.get(User, eintrag.user_id))
|
s.delete(s.get(User, eintrag.user_id))
|
||||||
s.commit()
|
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")
|
||||||
|
|||||||
@@ -17,7 +17,10 @@ EnvironmentFile=/etc/certain-finance/env
|
|||||||
# Adresse/Port kommen aus der EnvironmentFile. APP_BIND_HOST=0.0.0.0 macht den
|
# Adresse/Port kommen aus der EnvironmentFile. APP_BIND_HOST=0.0.0.0 macht den
|
||||||
# Dienst direkt über http://<LXC-IP>:<APP_PORT> erreichbar; 127.0.0.1 beschränkt
|
# Dienst direkt über http://<LXC-IP>:<APP_PORT> erreichbar; 127.0.0.1 beschränkt
|
||||||
# ihn auf einen Reverse-Proxy auf demselben Host.
|
# ihn auf einen Reverse-Proxy auf demselben Host.
|
||||||
ExecStart=/opt/certain-finance/backend/.venv/bin/uvicorn app.main:app --host ${APP_BIND_HOST} --port ${APP_PORT}
|
# --proxy-headers: Hinter einem Reverse Proxy sieht der Dienst sonst nur dessen
|
||||||
|
# interne Adresse. Fuer die Rueckleitung der SSO-Anmeldung zaehlt aber die
|
||||||
|
# oeffentliche – setz zusaetzlich APP_PUBLIC_BASE_URL, dann ist sie eindeutig.
|
||||||
|
ExecStart=/opt/certain-finance/backend/.venv/bin/uvicorn app.main:app --host ${APP_BIND_HOST} --port ${APP_PORT} --proxy-headers --forwarded-allow-ips=*
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
RestartSec=3
|
RestartSec=3
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,12 @@ APP_ECB_FETCH_ON_START=true
|
|||||||
# Wird vom Install-/Update-Skript gesetzt (kurzer Git-Commit, für /api/version).
|
# Wird vom Install-/Update-Skript gesetzt (kurzer Git-Commit, für /api/version).
|
||||||
APP_GIT_SHA=
|
APP_GIT_SHA=
|
||||||
|
|
||||||
|
# Unter welcher Adresse Certain von aussen erreichbar ist – ohne Pfad am Ende.
|
||||||
|
# Nur nötig für die SSO-Anmeldung (siehe unten), dort aber wichtig: Hinter einem
|
||||||
|
# Reverse Proxy käme sonst die INTERNE Adresse heraus (http://10.0.0.5:8071/…),
|
||||||
|
# und der Anmeldedienst weist die Rückleitung ab.
|
||||||
|
#APP_PUBLIC_BASE_URL=https://certain.example.com
|
||||||
|
|
||||||
# --- Anmeldung über einen fremden Dienst (OpenID Connect) --------------------
|
# --- Anmeldung über einen fremden Dienst (OpenID Connect) --------------------
|
||||||
#
|
#
|
||||||
# Optional. Bleibt APP_OIDC_PROVIDERS leer, ändert sich nichts: Es gibt weiter
|
# Optional. Bleibt APP_OIDC_PROVIDERS leer, ändert sich nichts: Es gibt weiter
|
||||||
|
|||||||
Reference in New Issue
Block a user