fix(auth): enrich device-auth timeout at the source to cover the dashboard poller
/simplify-code review found _poll_for_token has a second caller: web_server._nous_poller (dashboard/desktop device login), which surfaces str(e) as the UI error_message — so wrapping only in _nous_device_code_login left the dashboard showing the bare timeout. Move the enrichment into _poll_for_token's deadline raise so every caller inherits the guidance, and drop the now-redundant try/except wrap in the CLI login. Add a source-level regression test driving the real poll loop (authorization_pending stub client) to the deadline.
This commit is contained in:
parent
fbf26e3845
commit
840fb55a8a
|
|
@ -5123,7 +5123,10 @@ def _poll_for_token(
|
|||
description = error_payload.get("error_description") or "Unknown authentication error"
|
||||
raise RuntimeError(f"{error_code}: {description}")
|
||||
|
||||
raise TimeoutError("Timed out waiting for device authorization")
|
||||
# Enriched at the SOURCE so every caller inherits the guidance:
|
||||
# the CLI login (_nous_device_code_login) and the dashboard/desktop
|
||||
# poller (web_server._nous_poller, which surfaces str(e) to the UI).
|
||||
raise TimeoutError(_nous_device_auth_timeout_message(portal_base_url))
|
||||
|
||||
|
||||
# =============================================================================
|
||||
|
|
@ -8631,19 +8634,14 @@ def _nous_device_code_login(
|
|||
effective_interval = max(1, min(interval, DEVICE_AUTH_POLL_INTERVAL_CAP_SECONDS))
|
||||
print(f"Waiting for approval (polling every {effective_interval}s)...")
|
||||
|
||||
try:
|
||||
token_data = _poll_for_token(
|
||||
client=client,
|
||||
portal_base_url=portal_base_url,
|
||||
client_id=client_id,
|
||||
device_code=str(device_data["device_code"]),
|
||||
expires_in=expires_in,
|
||||
poll_interval=interval,
|
||||
)
|
||||
except TimeoutError as exc:
|
||||
raise TimeoutError(
|
||||
_nous_device_auth_timeout_message(portal_base_url)
|
||||
) from exc
|
||||
token_data = _poll_for_token(
|
||||
client=client,
|
||||
portal_base_url=portal_base_url,
|
||||
client_id=client_id,
|
||||
device_code=str(device_data["device_code"]),
|
||||
expires_in=expires_in,
|
||||
poll_interval=interval,
|
||||
)
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
token_expires_in = _coerce_ttl_seconds(token_data.get("expires_in", 0))
|
||||
|
|
|
|||
|
|
@ -1092,8 +1092,45 @@ class TestNousDeviceAuthTimeoutMessage:
|
|||
assert f"{DEFAULT_NOUS_PORTAL_URL.rstrip('/')}/login" in msg
|
||||
|
||||
|
||||
def test_poll_for_token_timeout_raises_actionable_message():
|
||||
"""The poll deadline must raise the CAPTCHA-aware guidance at the SOURCE,
|
||||
so both the CLI login and the dashboard poller (web_server._nous_poller,
|
||||
which surfaces str(e) to the UI) inherit it."""
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
import hermes_cli.auth as auth_mod
|
||||
|
||||
class _PendingClient:
|
||||
def post(self, url, data=None):
|
||||
request = httpx.Request("POST", url)
|
||||
return httpx.Response(
|
||||
400,
|
||||
json={"error": "authorization_pending"},
|
||||
request=request,
|
||||
)
|
||||
|
||||
from typing import cast
|
||||
|
||||
with pytest.raises(TimeoutError) as excinfo:
|
||||
auth_mod._poll_for_token(
|
||||
client=cast(httpx.Client, _PendingClient()),
|
||||
portal_base_url="https://portal.nousresearch.com",
|
||||
client_id="hermes-cli",
|
||||
device_code="device",
|
||||
expires_in=1,
|
||||
poll_interval=1,
|
||||
)
|
||||
|
||||
msg = str(excinfo.value)
|
||||
assert "CAPTCHA" in msg
|
||||
assert "hermes portal" in msg
|
||||
assert "https://portal.nousresearch.com/login" in msg
|
||||
|
||||
|
||||
def test_nous_device_code_login_timeout_raises_actionable_message(monkeypatch):
|
||||
"""Poll timeout must surface the CAPTCHA-aware guidance, not a bare line."""
|
||||
"""Poll timeout must surface the CAPTCHA-aware guidance through the CLI
|
||||
login flow (propagates unchanged from _poll_for_token)."""
|
||||
import pytest
|
||||
|
||||
import hermes_cli.auth as auth_mod
|
||||
|
|
@ -1115,7 +1152,11 @@ def test_nous_device_code_login_timeout_raises_actionable_message(monkeypatch):
|
|||
)
|
||||
|
||||
def _timeout(**kwargs):
|
||||
raise TimeoutError("Timed out waiting for device authorization")
|
||||
raise TimeoutError(
|
||||
auth_mod._nous_device_auth_timeout_message(
|
||||
kwargs.get("portal_base_url", "")
|
||||
)
|
||||
)
|
||||
|
||||
monkeypatch.setattr(auth_mod, "_poll_for_token", _timeout)
|
||||
monkeypatch.setattr(auth_mod.webbrowser, "open", lambda url: True)
|
||||
|
|
|
|||
Loading…
Reference in New Issue