diff --git a/backend/app/config.py b/backend/app/config.py index fb57822..c1a01ff 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -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. diff --git a/backend/app/routers/oidc.py b/backend/app/routers/oidc.py index defeb80..4b1432e 100644 --- a/backend/app/routers/oidc.py +++ b/backend/app/routers/oidc.py @@ -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: diff --git a/backend/tests/test_oidc.py b/backend/tests/test_oidc.py index 0801097..d51111b 100644 --- a/backend/tests/test_oidc.py +++ b/backend/tests/test_oidc.py @@ -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") diff --git a/deploy/certain-finance.service b/deploy/certain-finance.service index b2320ac..22a7328 100644 --- a/deploy/certain-finance.service +++ b/deploy/certain-finance.service @@ -17,7 +17,10 @@ EnvironmentFile=/etc/certain-finance/env # Adresse/Port kommen aus der EnvironmentFile. APP_BIND_HOST=0.0.0.0 macht den # Dienst direkt über http://: erreichbar; 127.0.0.1 beschränkt # 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 RestartSec=3 diff --git a/deploy/env.example b/deploy/env.example index cd6cabb..4a01959 100644 --- a/deploy/env.example +++ b/deploy/env.example @@ -37,6 +37,12 @@ APP_ECB_FETCH_ON_START=true # Wird vom Install-/Update-Skript gesetzt (kurzer Git-Commit, für /api/version). 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) -------------------- # # Optional. Bleibt APP_OIDC_PROVIDERS leer, ändert sich nichts: Es gibt weiter