feat: surface and decode the OAuth id_token in Connection Info (#2019)

When an authorization server returns an `id_token` alongside the access
token, the Inspector already persisted it and never showed it. Wire it
through to Connection Info as a viewer.

- Generalize `OAuthAccessTokenField` into `OAuthTokenField` (label + token),
  so the ID Token row reuses the existing copy control and "Decode JWT"
  toggle rather than duplicating them. Access-token behavior is unchanged.
- `oauthDetailsFromConnectionState` now surfaces `tokens.id_token` as
  `idToken`, omitted entirely when absent so no empty row renders.
- Docs: update the two `specification/` references to the renamed field.

This is deliberately a viewer, not a flow: no OIDC login mode, no signature
verification, and the `id_token` is never treated as a credential. The EMA
path is untouched. `id_token` is already in `maskSecrets`'
`JSON_SENSITIVE_KEYS`, so it stays masked in the Network tab — no gap.

Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-08-15 18:33:41 -04:00
commit 07cafeedbc

View file

@ -473,6 +473,60 @@ describe("ConnectionInfoContent", () => {
expect(screen.queryByText("Auth URL")).not.toBeInTheDocument();
expect(screen.queryByText("Scopes")).not.toBeInTheDocument();
expect(screen.queryByText("Access Token")).not.toBeInTheDocument();
expect(screen.queryByText("ID Token")).not.toBeInTheDocument();
});
it("renders the ID Token row when the token set carries one", () => {
renderWithMantine(
<ConnectionInfoContent
initializeResult={fullResult}
clientCapabilities={fullClientCaps}
transport="streamable-http"
oauth={{
protocol: "standard",
authorized: true,
accessToken: "token-123",
idToken: "id-token-456",
}}
/>,
);
expect(screen.getByText("Access Token")).toBeInTheDocument();
expect(screen.getByText("ID Token")).toBeInTheDocument();
expect(screen.getByText("id-token-456")).toBeInTheDocument();
});
it("renders the ID Token row on its own when there is no access token", () => {
renderWithMantine(
<ConnectionInfoContent
initializeResult={fullResult}
clientCapabilities={fullClientCaps}
transport="streamable-http"
oauth={{
protocol: "standard",
authorized: true,
idToken: "id-token-456",
}}
/>,
);
expect(screen.queryByText("Access Token")).not.toBeInTheDocument();
expect(screen.getByText("ID Token")).toBeInTheDocument();
});
it("omits the ID Token row when only an access token is present", () => {
renderWithMantine(
<ConnectionInfoContent
initializeResult={fullResult}
clientCapabilities={fullClientCaps}
transport="streamable-http"
oauth={{
protocol: "standard",
authorized: true,
accessToken: "token-123",
}}
/>,
);
expect(screen.getByText("Access Token")).toBeInTheDocument();
expect(screen.queryByText("ID Token")).not.toBeInTheDocument();
});
it("does not render OAuth section when oauth prop is omitted", () => {