Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions hermes_cli/dashboard_auth/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,16 @@ def _auto_sso_response(request: Request) -> Response | None:
# Zero β†’ nothing to redirect to. Two+ β†’ user must choose at /login.
return None

from hermes_cli.dashboard_auth.prefix import prefix_from_request

provider = providers[0]
# A password-only provider (e.g. basic auth) cannot participate in the
# OAuth redirect flow β€” ``start_login`` raises NotImplementedError.
# Skip the auto-redirect so the /login page renders the password form
# (which POSTs to /auth/password-login) instead of bouncing the user
# to a 500.
if provider.supports_password:
return None

from hermes_cli.dashboard_auth.prefix import prefix_from_request
prefix = prefix_from_request(request)
next_param = _safe_next_target(request)
from urllib.parse import quote
Expand Down
20 changes: 20 additions & 0 deletions tests/hermes_cli/test_dashboard_auth_401_reauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,26 @@ class _SecondStub(StubAuthProvider):
assert r.headers["location"].startswith("/login")
assert "/auth/login" not in r.headers["location"]

def test_password_provider_skips_auto_sso(self, gated_app):
"""A password-only provider must NOT be auto-redirected to the OAuth
initiation route (/auth/login) β€” basic auth cannot participate in the
OAuth redirect flow and ``start_login`` raises NotImplementedError.
Instead, the middleware should fall through to the /login interstitial
which renders the password form (POSTing to /auth/password-login)."""
from hermes_cli.dashboard_auth import clear_providers, register_provider

class _PasswordStub(StubAuthProvider):
name = "password-stub"
display_name = "Password Stub"
supports_password = True

clear_providers()
register_provider(_PasswordStub())
r = gated_app.get("/sessions", follow_redirects=False)
# Must NOT redirect to /auth/login (the OAuth route) β€” that would 500.
assert r.headers["location"].startswith("/login")
assert "/auth/login" not in r.headers["location"]


# ---------------------------------------------------------------------------
# Gate middleware: same-origin next= validation
Expand Down