hermes-agent/tests/hermes_cli/test_xai_oauth_refresh.py
Jaaneek 5ef0b8acb0 feat(auth): make xAI Grok OAuth device-code-only, drop loopback login
Replace the loopback/PKCE-callback server and manual-paste fallback with
the RFC 8628 device-code flow as the only xAI Grok OAuth login path. The
flow works in headless/SSH/container sessions with no 127.0.0.1 listener,
shrinking the local attack surface.

- Poll the token endpoint with server-provided interval, honoring
  slow_down and expires_in; store tokens with auth_mode
  oauth_device_code.
- Adaptive proactive refresh skew for short-lived device-code JWTs;
  rotated tokens sync back to auth.json, the global root store, and the
  credential pool (no refresh-token replay).
- Clear source suppression on successful re-login (CLI + dashboard) and
  drop the duplicate dashboard pool entry so exactly one seeded
  device_code entry exists.
- Use the shared device_code source name for consistency with the
  nous/codex device-code providers.
- Desktop: remove the loopback OAuth flow states and dead type variants;
  pkce providers' sign-in URL selection is unchanged.
- Docs (EN + zh-Hans) rewritten for device-code login; drop the deleted
  --manual-paste flag from documented commands.
2026-07-02 13:17:41 -07:00

57 lines
1.5 KiB
Python

from __future__ import annotations
import base64
import json
import time
from hermes_cli import auth
def _jwt_with_exp(exp: int) -> str:
header = (
base64.urlsafe_b64encode(json.dumps({"alg": "none"}).encode())
.decode()
.rstrip("=")
)
payload = (
base64.urlsafe_b64encode(json.dumps({"exp": exp}).encode())
.decode()
.rstrip("=")
)
return f"{header}.{payload}.sig"
def test_xai_oauth_refresh_skew_is_one_hour() -> None:
assert auth.XAI_ACCESS_TOKEN_REFRESH_SKEW_SECONDS == 3600
def test_xai_oauth_token_expiring_uses_one_hour_skew() -> None:
token = _jwt_with_exp(int(time.time()) + 30 * 60)
assert auth._xai_access_token_is_expiring(
token,
auth.XAI_ACCESS_TOKEN_REFRESH_SKEW_SECONDS,
)
def test_xai_oauth_token_not_expiring_beyond_one_hour_skew() -> None:
token = _jwt_with_exp(int(time.time()) + 90 * 60)
assert not auth._xai_access_token_is_expiring(
token,
auth.XAI_ACCESS_TOKEN_REFRESH_SKEW_SECONDS,
)
def test_xai_proactive_refresh_skew_short_lived_token() -> None:
token = _jwt_with_exp(int(time.time()) + 15 * 60)
skew = auth._xai_proactive_refresh_skew_seconds(token)
assert skew == 120
assert not auth._xai_access_token_is_expiring(token, skew)
def test_xai_proactive_refresh_skew_long_lived_token() -> None:
token = _jwt_with_exp(int(time.time()) + 5 * 60 * 60)
assert auth._xai_proactive_refresh_skew_seconds(token) == auth.XAI_ACCESS_TOKEN_REFRESH_SKEW_SECONDS