mirror of
https://github.com/open-jarvis/OpenJarvis.git
synced 2026-07-28 14:07:55 +00:00
`AuthMiddleware` is a BaseHTTPMiddleware and never intercepts WebSocket
upgrade requests, so `/v1/chat/stream` and `/v1/agents/events` accepted any
connection — leaking all agent events/message content and allowing
unauthenticated inference even when an API key was configured for HTTP. The
A2A JSON-RPC server likewise dispatched every request without auth.
- Add `websocket_authorized(websocket, expected_key)` (constant-time compare)
and check it in both WS handlers BEFORE `accept()`, closing with code 1008
on failure. Token is read from `?token=` (browsers can't set WS headers) or
an `Authorization: Bearer` header. `create_app` now exposes the key via
`app.state.api_key`; when empty, auth is disabled, matching the HTTP
middleware's local-default behavior (so loopback dev is unchanged).
- A2AServer gains an optional `auth_token`: `handle_request(token=...)`
rejects with JSON-RPC -32001 before dispatch when configured, advertises
`{"schemes": ["bearer"]}` on the agent card, and stays open when unset.
Added `A2AConfig.auth_token`.
Verified empirically against the real mounted endpoints via TestClient: no
token / wrong token are rejected at the handshake (WebSocketDisconnect),
correct token streams normally, and no-key configs still connect freely.
Closes #217
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
52 lines
1.5 KiB
Python
52 lines
1.5 KiB
Python
"""A2A server authentication tests (issue #217)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from openjarvis.a2a.protocol import AgentCard
|
|
from openjarvis.a2a.server import A2AServer
|
|
|
|
|
|
def _request():
|
|
return {
|
|
"jsonrpc": "2.0",
|
|
"id": "1",
|
|
"method": "tasks/send",
|
|
"params": {"input": "ping"},
|
|
}
|
|
|
|
|
|
def test_no_auth_token_allows_requests():
|
|
server = A2AServer(AgentCard(name="x"), handler=lambda t: f"echo:{t}")
|
|
resp = server.handle_request(_request())
|
|
assert resp.get("error") is None
|
|
|
|
|
|
def test_missing_token_rejected_when_required():
|
|
server = A2AServer(AgentCard(name="x"), handler=lambda t: t, auth_token="sek")
|
|
resp = server.handle_request(_request())
|
|
assert resp["error"]["code"] == -32001
|
|
|
|
|
|
def test_wrong_token_rejected():
|
|
server = A2AServer(AgentCard(name="x"), handler=lambda t: t, auth_token="sek")
|
|
resp = server.handle_request(_request(), token="nope")
|
|
assert resp["error"]["code"] == -32001
|
|
|
|
|
|
def test_correct_token_accepted_and_dispatched():
|
|
server = A2AServer(
|
|
AgentCard(name="x"), handler=lambda t: f"echo:{t}", auth_token="sek"
|
|
)
|
|
resp = server.handle_request(_request(), token="sek")
|
|
assert resp.get("error") is None
|
|
|
|
|
|
def test_auth_scheme_advertised_on_card():
|
|
server = A2AServer(AgentCard(name="x"), auth_token="sek")
|
|
assert server.agent_card.authentication == {"schemes": ["bearer"]}
|
|
|
|
|
|
def test_no_token_unauthenticated_card_stays_empty():
|
|
server = A2AServer(AgentCard(name="x"))
|
|
assert server.agent_card.authentication == {}
|