From f3d2dfbec670cd520bf23fe6ec1f4ce918d286bd Mon Sep 17 00:00:00 2001 From: Joey Kerper Date: Mon, 29 Jun 2026 19:45:11 -0400 Subject: [PATCH] fix(dashboard_auth): allow any http:// host in self-hosted OIDC redirect_uri (#55099) The self-hosted OIDC dashboard login rejected any http:// redirect_uri whose host was not localhost/127.0.0.1, surfacing "redirect_uri may only use http:// for localhost/127.0.0.1" before reaching the IDP. This broke self-hosted dashboards reached over plain HTTP (including LAN IPs, internal hostnames, and reverse proxies that terminate TLS upstream). #38827 already dropped this check from the nous provider, but the generic self-hosted provider copied the old localhost-only branch and reintroduced the bug for HERMES_DASHBOARD_OIDC_ISSUER setups. The IDP's own allowlist is authoritative on which redirect_uris are permitted; this client-side _validate_redirect_uri is only a fast-fail for obvious operator error and should not second-guess valid http:// deployments. Fix: drop the localhost-only branch on the http scheme. Validation now enforces only that the scheme is http(s) and the path ends with /auth/callback. Updated the docstring to explain the relaxed contract, and added test_allows_http_with_arbitrary_host covering an internal hostname and a LAN IP alongside the existing localhost case. --- plugins/dashboard_auth/self_hosted/__init__.py | 14 +++++--------- .../dashboard_auth/test_self_hosted_provider.py | 10 ++++++++++ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/plugins/dashboard_auth/self_hosted/__init__.py b/plugins/dashboard_auth/self_hosted/__init__.py index ebfa6876021..325c6536d7e 100644 --- a/plugins/dashboard_auth/self_hosted/__init__.py +++ b/plugins/dashboard_auth/self_hosted/__init__.py @@ -610,21 +610,17 @@ class SelfHostedOIDCProvider(DashboardAuthProvider): """Fast-fail obviously-broken redirect_uris before bouncing to the IDP. The IDP's own allowlist is authoritative; this just catches the common - operator-error case with a clear message. Mirrors the nous provider. + operator-error case with a clear message. We allow any ``http://`` host + (not just localhost) so self-hosted dashboards reached over plain HTTP — + LAN IPs, internal hostnames, reverse proxies that terminate TLS upstream + — are not rejected here; the IDP makes the final call on which + redirect_uris are permitted. Mirrors the nous provider. """ parsed = urllib.parse.urlparse(redirect_uri) if parsed.scheme not in ("https", "http"): raise ProviderError( f"redirect_uri must be http(s), got {redirect_uri!r}" ) - if parsed.scheme == "http" and parsed.hostname not in ( - "localhost", - "127.0.0.1", - ): - raise ProviderError( - "redirect_uri may only use http:// for localhost/127.0.0.1, " - f"got {redirect_uri!r}" - ) if not parsed.path or not parsed.path.endswith("/auth/callback"): raise ProviderError( "redirect_uri path must end with '/auth/callback', " diff --git a/tests/plugins/dashboard_auth/test_self_hosted_provider.py b/tests/plugins/dashboard_auth/test_self_hosted_provider.py index d20e6eb1c25..02e4908c296 100644 --- a/tests/plugins/dashboard_auth/test_self_hosted_provider.py +++ b/tests/plugins/dashboard_auth/test_self_hosted_provider.py @@ -496,6 +496,16 @@ class TestStartLogin: with pytest.raises(ProviderError, match="/auth/callback"): provider.start_login(redirect_uri="https://x.example/oauth/cb") + def test_allows_http_with_arbitrary_host(self, provider): + # http:// is permitted for any host now, not just localhost — the + # IDP-side allowlist is authoritative on which redirect_uris are + # accepted; this client-side fast-fail must not reject self-hosted + # dashboards reached over plain HTTP (LAN IPs, internal hostnames, + # TLS-terminating reverse proxies). Should not raise. + provider.start_login(redirect_uri="http://hermes.example/auth/callback") + provider.start_login(redirect_uri="http://192.168.1.50:9119/auth/callback") + provider.start_login(redirect_uri="http://my-internal-host/auth/callback") + def test_allows_http_localhost_redirect(self, provider): provider.start_login(redirect_uri="http://localhost:8080/auth/callback") provider.start_login(redirect_uri="http://127.0.0.1:8080/auth/callback")