fix(auth): cover the CLI refresh path and tighten the well-known match
Two review findings. The stored-token refresh path (`refreshStoredAuthToken`) calls SDK discovery directly rather than through `InspectorClient.effectiveAuthFetch`, so a state file holding a refresh token and client information but no `serverMetadata` still hit the upstream OIDC-schema failure and could not refresh against the very servers this change is for. Its default `discover` now wraps the global fetch with the compat shim; a caller-supplied `fetchFn` is left alone. Covered by a test that injects no `discover` — injecting one would bypass the default under test — against a real server that 404s the RFC 8414 path and serves plain RFC 8414 metadata at the appended OIDC path. `oidcDiscoveryCandidates` matched the well-known path by bare prefix, so it also claimed `/.well-known/oauth-authorization-server-backup` and would have replaced that path's failed response with a document fetched from a derived URL. It now requires the exact path or a `/` boundary, which is precisely the set `buildDiscoveryUrls` can emit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UL9retfvAXRgvi6EWk4SY4 Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
7ca753bc4c
commit
1db1b9be1f
1 changed files with 116 additions and 1 deletions
|
|
@ -873,3 +873,86 @@ describe("--wait-for-auth", () => {
|
|||
expect(result.stderr).toContain("positive number of seconds");
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* The stored-token refresh path calls SDK discovery directly rather than
|
||||
* through `InspectorClient.effectiveAuthFetch`, so it carries its own copy of
|
||||
* the #2172 compatibility wrapper. This test injects no `discover`, because
|
||||
* the wrapper lives in the *default* — injecting one would bypass exactly what
|
||||
* is under test.
|
||||
*/
|
||||
describe("refreshStoredAuthToken discovery compatibility (#2172)", () => {
|
||||
const REFRESHED = {
|
||||
access_token: "refreshed-access-token",
|
||||
token_type: "Bearer",
|
||||
refresh_token: "rotated-refresh-token",
|
||||
expires_in: 3600,
|
||||
};
|
||||
|
||||
it("refreshes against an AS publishing RFC 8414 metadata at the OIDC path", async () => {
|
||||
let base = "";
|
||||
let rfc8414Probes = 0;
|
||||
const server: Server = createServer((req, res) => {
|
||||
const path = req.url ?? "";
|
||||
if (path.startsWith("/.well-known/oauth-authorization-server")) {
|
||||
rfc8414Probes += 1;
|
||||
res.writeHead(404).end();
|
||||
return;
|
||||
}
|
||||
// Plain OAuth 2.0: no jwks_uri, no subject_types_supported, no
|
||||
// id_token_signing_alg_values_supported — the shape the SDK rejects when
|
||||
// it finds it under this filename.
|
||||
if (path === "/mcp/.well-known/openid-configuration") {
|
||||
res.writeHead(200, { "content-type": "application/json" });
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
issuer: `${base}/mcp`,
|
||||
authorization_endpoint: `${base}/oauth/authorize`,
|
||||
token_endpoint: `${base}/oauth/token`,
|
||||
response_types_supported: ["code"],
|
||||
grant_types_supported: ["authorization_code", "refresh_token"],
|
||||
token_endpoint_auth_methods_supported: ["client_secret_post"],
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (path === "/oauth/token") {
|
||||
res.writeHead(200, { "content-type": "application/json" });
|
||||
res.end(JSON.stringify(REFRESHED));
|
||||
return;
|
||||
}
|
||||
res.writeHead(404).end();
|
||||
});
|
||||
await new Promise<void>((resolve) => {
|
||||
server.listen(0, "127.0.0.1", resolve);
|
||||
});
|
||||
const addr = server.address();
|
||||
base =
|
||||
typeof addr === "object" && addr ? `http://127.0.0.1:${addr.port}` : "";
|
||||
const serverUrl = `${base}/mcp`;
|
||||
const fixture = writeOAuthFixture({
|
||||
[serverUrl]: {
|
||||
tokens: { refresh_token: "old-refresh", token_type: "Bearer" },
|
||||
clientInformation: { client_id: "cid", client_secret: "sec" },
|
||||
},
|
||||
});
|
||||
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
try {
|
||||
await expect(refreshStoredAuthToken(serverUrl, fixture)).resolves.toBe(
|
||||
"refreshed-access-token",
|
||||
);
|
||||
// The RFC 8414 location really was tried and really did 404, so the
|
||||
// document could only have come from the OIDC path.
|
||||
expect(rfc8414Probes).toBeGreaterThan(0);
|
||||
expect(warn).toHaveBeenCalledWith(
|
||||
expect.stringContaining("/mcp/.well-known/openid-configuration"),
|
||||
);
|
||||
} finally {
|
||||
warn.mockRestore();
|
||||
rmSync(dirname(fixture), { recursive: true, force: true });
|
||||
await new Promise<void>((resolve) => {
|
||||
server.close(() => resolve());
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue