Serve MCP and hooks with a secret through the portal tunnel
The connector now serves /mcp, its OAuth documents and /hooks/ besides /api/v1/, refuses any dot segment (httpx resolves them, which carried /api/v1/../../mcp onto the MCP mount), addresses calls to the hub's host, and bridges served sockets by calling the app with a websocket scope. A hook without a secret answers the tunnel with the 404 (or 1008) of a wrong secret. Requests through the hub get the hub's issuer, resource and consent page in the OAuth metadata and the 401, and the MCP host check admits the hub while enrolled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WgBfkm4rCmtd1T4vHWjEPx
This commit is contained in:
@@ -80,6 +80,44 @@ def test_the_metadata_says_how_to_get_in(client: TestClient) -> None:
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("enrolled")
|
||||
def test_through_the_hub_the_metadata_and_resource_are_the_hubs(
|
||||
client: TestClient, superuser_token_headers: dict[str, str]
|
||||
) -> None:
|
||||
"""An agent at /i/{id}/mcp on the hub is sent to the hub's addresses."""
|
||||
from fluksio.cloud import config as cloud_config
|
||||
from tests.utils.portal import INSTANCE_ID, ISSUER
|
||||
|
||||
via = {cloud_config.VIA_HEADER: cloud_config.VIA_PORTAL}
|
||||
base = f"{ISSUER}/i/{INSTANCE_ID}"
|
||||
document = client.get("/.well-known/oauth-authorization-server", headers=via).json()
|
||||
assert document["issuer"] == base
|
||||
assert document["token_endpoint"] == f"{base}{PREFIX}/token"
|
||||
# No link is attached here to name the portal's page, so the hub's own.
|
||||
assert document["authorization_endpoint"] == f"{base}/oauth/authorize"
|
||||
# A request made here hears nothing about the hub.
|
||||
local = client.get("/.well-known/oauth-authorization-server").json()
|
||||
assert local["issuer"] == settings.oauth_issuer
|
||||
|
||||
_verifier, challenge = pkce()
|
||||
body = {
|
||||
"client_id": register(client)["client_id"],
|
||||
"redirect_uri": REDIRECT,
|
||||
"code_challenge": challenge,
|
||||
"code_challenge_method": "S256",
|
||||
"resource": f"{base}/mcp",
|
||||
}
|
||||
through = client.post(
|
||||
f"{PREFIX}/authorize", headers={**superuser_token_headers, **via}, json=body
|
||||
)
|
||||
assert through.status_code == 200, through.text
|
||||
# Only through the hub is that a resource this instance serves.
|
||||
here = client.post(
|
||||
f"{PREFIX}/authorize", headers=superuser_token_headers, json=body
|
||||
)
|
||||
assert here.json()["error"] == "invalid_target"
|
||||
|
||||
|
||||
def test_a_full_authorization_reaches_the_api(
|
||||
client: TestClient, superuser_token_headers: dict[str, str]
|
||||
) -> None:
|
||||
|
||||
Reference in New Issue
Block a user