feat(ws): accept API tokens on /ws/companion_frame

The companion frame proxy only accepted an operator JWT in `?token=`,
while `/ws/packets` also accepts a long-lived API token (X-API-Key
header, or the same query parameter). Apply the identical rule to the
frame proxy so a client that already holds a repeater API key can open
the companion link on the HTTP port without keeping a JWT session alive.

No behaviour change for JWT clients. Same fallback order, close codes
and log lines as PacketWebSocket.
This commit is contained in:
Treehouse-00 committed 2026-09-10 10:22:20 -07:00
1 parent 4705c99c38
commit 56e2fd6417
2 files changed
+75 -12

No files matched your search

+49 -1
View File
@@ -14,9 +14,11 @@ def cp_cfg(monkeypatch):
return cfg
def _ws(query_string):
def _ws(query_string, api_key=None):
ws = object.__new__(proxy.CompanionFrameWebSocket)
ws.environ = {"QUERY_STRING": query_string}
if api_key is not None:
ws.environ["HTTP_X_API_KEY"] = api_key
ws.close = MagicMock()
ws.send = MagicMock()
ws._teardown = MagicMock()
@@ -43,6 +45,52 @@ def test_opened_rejects_invalid_token(cp_cfg):
ws.close.assert_called_once_with(code=1008, reason="unauthorized")
def test_opened_accepts_api_key_header(cp_cfg, monkeypatch):
cp_cfg["jwt_handler"] = SimpleNamespace(verify_jwt=lambda _t: None)
cp_cfg["token_manager"] = SimpleNamespace(
verify_token=lambda t: {"name": "waev"} if t == "k" else None
)
ws = _ws("companion_name=c1", api_key="k")
ws._resolve_tcp_endpoint = MagicMock(return_value=("127.0.0.1", 5000))
fake_socket = MagicMock()
monkeypatch.setattr(proxy.socket, "socket", lambda *_args, **_kwargs: fake_socket)
monkeypatch.setattr(proxy.threading, "Thread", lambda **_kw: MagicMock())
ws.opened()
ws.close.assert_not_called()
assert ws._companion_name == "c1"
def test_opened_accepts_api_token_in_query_like_packet_ws(cp_cfg, monkeypatch):
cp_cfg["jwt_handler"] = SimpleNamespace(verify_jwt=lambda _t: None)
cp_cfg["token_manager"] = SimpleNamespace(verify_token=lambda _t: {"name": "waev"})
ws = _ws("token=k&companion_name=c1")
ws._resolve_tcp_endpoint = MagicMock(return_value=("127.0.0.1", 5000))
fake_socket = MagicMock()
monkeypatch.setattr(proxy.socket, "socket", lambda *_args, **_kwargs: fake_socket)
monkeypatch.setattr(proxy.threading, "Thread", lambda **_kw: MagicMock())
ws.opened()
ws.close.assert_not_called()
def test_opened_rejects_unknown_api_key(cp_cfg):
cp_cfg["jwt_handler"] = SimpleNamespace(verify_jwt=lambda _t: None)
cp_cfg["token_manager"] = SimpleNamespace(verify_token=lambda _t: None)
ws = _ws("companion_name=c1", api_key="bad")
ws.opened()
ws.close.assert_called_once_with(code=1008, reason="unauthorized")
def test_opened_rejects_api_key_without_token_manager(cp_cfg):
cp_cfg["jwt_handler"] = SimpleNamespace(verify_jwt=lambda _t: None)
ws = _ws("companion_name=c1", api_key="k")
ws.opened()
ws.close.assert_called_once_with(code=1008, reason="unauthorized")
def test_opened_rejects_missing_companion_name(cp_cfg):
cp_cfg["jwt_handler"] = SimpleNamespace(verify_jwt=lambda _t: {"sub": "u"})
ws = _ws("token=t")