mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-29 06:31:32 +00:00
fix(auth): improve xAI OAuth SSH hint with visual header and auto-detected host
This commit is contained in:
parent
bf6eeb3f93
commit
226680500d
2 changed files with 78 additions and 6 deletions
|
|
@ -2895,6 +2895,21 @@ def _is_remote_session() -> bool:
|
|||
return bool(os.getenv("SSH_CLIENT") or os.getenv("SSH_TTY"))
|
||||
|
||||
|
||||
def _ssh_user_at_host() -> str:
|
||||
"""Return best-effort 'user@hostname' for the SSH tunnel hint command.
|
||||
|
||||
Falls back to placeholder tokens when the values cannot be determined so
|
||||
the hint is always syntactically valid even if not copy-pasteable.
|
||||
"""
|
||||
try:
|
||||
import socket as _socket
|
||||
hostname = _socket.gethostname() or "<this-host>"
|
||||
except OSError:
|
||||
hostname = "<this-host>"
|
||||
user = os.getenv("USER") or os.getenv("LOGNAME") or "<user>"
|
||||
return f"{user}@{hostname}"
|
||||
|
||||
|
||||
def _print_loopback_ssh_hint(redirect_uri: str, *, docs_url: str | None = None) -> None:
|
||||
"""Print an SSH tunnel hint when running a loopback-redirect OAuth flow on a
|
||||
remote host. The auth server (xAI, Spotify, ...) will redirect the user's
|
||||
|
|
@ -2918,19 +2933,22 @@ def _print_loopback_ssh_hint(redirect_uri: str, *, docs_url: str | None = None)
|
|||
port = parsed.port
|
||||
if host not in {"127.0.0.1", "::1", "localhost"} or not port:
|
||||
return
|
||||
divider = "-" * 60
|
||||
print()
|
||||
print("Remote session detected. Your browser will redirect to")
|
||||
print(f" {redirect_uri}")
|
||||
print("which the loopback listener on THIS machine is waiting on. If your")
|
||||
print("browser is on a different machine, forward the port first from your")
|
||||
print("local machine in a separate terminal:")
|
||||
print(divider)
|
||||
print("Remote session detected — SSH tunnel required")
|
||||
print(divider)
|
||||
print(f"Hermes is waiting for the OAuth callback on {redirect_uri}")
|
||||
print("but your browser is on a different machine. Run this command")
|
||||
print("in a NEW terminal on your local machine BEFORE opening the URL:")
|
||||
print()
|
||||
print(f" ssh -N -L {port}:127.0.0.1:{port} <user>@<this-host>")
|
||||
print(f" ssh -N -L {port}:127.0.0.1:{port} {_ssh_user_at_host()}")
|
||||
print()
|
||||
print("Then open the authorize URL above in your local browser.")
|
||||
if docs_url:
|
||||
print(f"Provider docs: {docs_url}")
|
||||
print(f"SSH/jump-box guide: {OAUTH_OVER_SSH_DOCS_URL}")
|
||||
print(divider)
|
||||
print()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from __future__ import annotations
|
|||
|
||||
import io
|
||||
import contextlib
|
||||
import socket
|
||||
|
||||
import pytest
|
||||
|
||||
|
|
@ -93,3 +94,56 @@ def test_loopback_ssh_hint_accepts_localhost_hostname(monkeypatch):
|
|||
"http://localhost:56121/callback"
|
||||
))
|
||||
assert "ssh -N -L 56121:127.0.0.1:56121" in out
|
||||
|
||||
|
||||
def test_loopback_ssh_hint_includes_user_at_host(monkeypatch):
|
||||
"""The SSH command should include a detected user@host so the user can
|
||||
copy-paste it without manually substituting placeholders."""
|
||||
monkeypatch.setattr(auth_mod, "_is_remote_session", lambda: True)
|
||||
monkeypatch.setattr(auth_mod, "_ssh_user_at_host", lambda: "alice@myserver.lan")
|
||||
out = _cap(lambda: auth_mod._print_loopback_ssh_hint(
|
||||
"http://127.0.0.1:56121/callback"
|
||||
))
|
||||
assert "ssh -N -L 56121:127.0.0.1:56121 alice@myserver.lan" in out
|
||||
|
||||
|
||||
def test_loopback_ssh_hint_has_visual_header(monkeypatch):
|
||||
"""The hint should print a divider and header so it stands out in noisy output."""
|
||||
monkeypatch.setattr(auth_mod, "_is_remote_session", lambda: True)
|
||||
out = _cap(lambda: auth_mod._print_loopback_ssh_hint(
|
||||
"http://127.0.0.1:56121/callback"
|
||||
))
|
||||
assert "Remote session detected" in out
|
||||
assert "---" in out # divider is present
|
||||
|
||||
|
||||
class TestSshUserAtHost:
|
||||
def test_resolves_user_and_hostname(self, monkeypatch):
|
||||
monkeypatch.setenv("USER", "alice")
|
||||
monkeypatch.delenv("LOGNAME", raising=False)
|
||||
monkeypatch.setattr(socket, "gethostname", lambda: "myserver")
|
||||
assert auth_mod._ssh_user_at_host() == "alice@myserver"
|
||||
|
||||
def test_falls_back_to_logname(self, monkeypatch):
|
||||
monkeypatch.delenv("USER", raising=False)
|
||||
monkeypatch.setenv("LOGNAME", "bob")
|
||||
monkeypatch.setattr(socket, "gethostname", lambda: "host1")
|
||||
assert auth_mod._ssh_user_at_host() == "bob@host1"
|
||||
|
||||
def test_placeholder_when_no_env_vars(self, monkeypatch):
|
||||
monkeypatch.delenv("USER", raising=False)
|
||||
monkeypatch.delenv("LOGNAME", raising=False)
|
||||
monkeypatch.setattr(socket, "gethostname", lambda: "host1")
|
||||
assert auth_mod._ssh_user_at_host() == "<user>@host1"
|
||||
|
||||
def test_placeholder_when_socket_raises(self, monkeypatch):
|
||||
monkeypatch.setenv("USER", "charlie")
|
||||
def _raise():
|
||||
raise OSError("no network")
|
||||
monkeypatch.setattr(socket, "gethostname", _raise)
|
||||
assert auth_mod._ssh_user_at_host() == "charlie@<this-host>"
|
||||
|
||||
def test_placeholder_when_empty_hostname(self, monkeypatch):
|
||||
monkeypatch.setenv("USER", "dave")
|
||||
monkeypatch.setattr(socket, "gethostname", lambda: "")
|
||||
assert auth_mod._ssh_user_at_host() == "dave@<this-host>"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue