Repository navigation
feat: add shared OAuth connection validation - #220
Conversation
marekdano
left a comment
There was a problem hiding this comment.
Findings
Suggestions
-
A gateway ID mismatch is reported as a network error (
src/hooks/useOAuthConnectionValidation.ts:673,:689)
throw new Error("Gateway mismatch")isn't anApiError, so the catch block maps it tonetwork. The user sees "could not reach the backend", which is wrong. Any non-ApiErrorthrown by a resolver function ends up the same way. Map it to a distinct code, such asgateway_unavailableorrequest_invalid, and add a test for it. -
A response with an unknown stage value can show a missing translation (
src/components/servers/OAuthConnectionValidationPanel.tsx:393)
If the backend returns a stage value outsideok/failed/skipped/unsupported, the hook correctly flags the result asincompatible. The panel still rendersresult.stages, andoutcome.${status}has no key for that value. react-intl then shows the raw message id and logs an error. Fall back toskipped, or hide the stage list when the stages aren't valid. -
A "success" response with a non-
okstage gets a generic error (src/hooks/useOAuthConnectionValidation.ts:679)
Whensuccess: truebuttools_listisskippedorunsupported,result.errorCodeis usually undefined. The user then sees the generic "OAuth connection failed…" message, even thoughtools_unsupportedexists. Consider deriving the code from the failing stage when the backend sends none. The test at line 481 only assertsstate === "failure". -
oauthConfigurationKeydepends on key order (src/lib/oauthConnectionValidation.ts:962)
JSON.stringify(server.oauthConfig)changes if the backend reorders keys between fetches, which would reset a valid result for no reason.oauthConfigurationKeyandcanValidateOAuthConnectionare also unused and untested in this PR. If #221 and #222 are the only consumers, either note that in the description or add small unit tests here.
Nits
success!in the hook tests is unnecessary becausesuccessis a typed constant.- The BFF test in
server/test/oauth-callback.test.tsonly shows that a mocked backend body passes through the proxy. Rename it, or add a case with an unsafe field, since "safe" isn't actually exercised. test()replaces the entry with{ state: "running" }, so the last result disappears while retesting. Confirm that's intended.
98e8ef7 to
27b885e
Compare
| return ( | ||
| <li key={stage} className="flex items-center gap-1.5"> | ||
| <Icon | ||
| className={`size-4 ${status === "ok" ? "text-success" : status === "failed" ? "text-destructive" : "text-muted-foreground"}`} |
There was a problem hiding this comment.
use STATUS_ICON and STATUS_TONE_CLASS (from @/lib/status) for setting the colours for consistency.
| <Button type="button" variant="outline" size="xs" disabled={running} onClick={onTest}> | ||
| {running && <Loader2 className="size-3.5 animate-spin" aria-hidden="true" />} | ||
| {message(running ? "testing" : entry ? "retest" : "test")} | ||
| </Button> | ||
| {running && ( | ||
| <Button type="button" variant="ghost" size="xs" onClick={onCancel}> | ||
| {message("cancel")} | ||
| </Button> | ||
| )} |
There was a problem hiding this comment.
If a user clicks Test connection, the button becomes disabled, and focus drops to <body>. Clicking Cancel unmounts that button as well. In both cases, a11y users have to navigate back from the top of the page. Keep the button focusable with aria-disabled and ignore clicks while running, or move focus to Cancel on start and back to the Test button on cancel/finish:
<Button aria-disabled={running} onClick={running ? undefined : onTest}>27b885e to
7a0744e
Compare
bbb6554 to
cdd676b
Compare
a589e7b to
bb2b09e
Compare
bb2b09e to
1adbba2
Compare
marekdano
left a comment
There was a problem hiding this comment.
Couple of findings listed below
| 401: "unauthenticated", | ||
| 403: "permission_denied", | ||
| 422: "request_invalid", | ||
| 429: "rate_limited", | ||
| 404: "gateway_unavailable", |
There was a problem hiding this comment.
Only 401, 403, 404, 422 and 429 get specific codes. Any other ApiError, such as a 502, 503 or 504 from the gateway or BFF, falls through to "generic" (line 108).
gateway_unavailable and timeout already exist and have localized messages. Consider mapping 502 and 503 to gateway_unavailable and 504 to timeout, with a test for each.
| let resolvingTarget = true; | ||
| try { | ||
| const resolved = typeof target === "function" ? await target(controller.signal) : target; | ||
| if (!isCurrent()) return; | ||
| if (resolved.gatewayId !== id) throw new ApiError(422, null, "Gateway mismatch"); | ||
| resolvingTarget = false; |
There was a problem hiding this comment.
lines 66,71,109 (cause: src/api/servers.ts:225)
A non-ApiError thrown after resolvingTarget = false is shown as "network". validateServerId(gatewayId) runs inside testOAuthConnection (servers.ts:225), so an invalid ID throws a plain Error that is reported as a network failure. A gateway mismatch (line 70) reports request_invalid, so the two are inconsistent. Validate the ID before the request, or catch it separately.
Signed-off-by: Vishu Bhatnagar <vishu.bhatnagar@ibm.com>
Signed-off-by: Vishu Bhatnagar <vishu.bhatnagar@ibm.com>
Signed-off-by: Vishu Bhatnagar <vishu.bhatnagar@ibm.com>
1adbba2 to
3eb8aa6
Compare
marekdano
left a comment
There was a problem hiding this comment.
The PR looks good now!
LGTM 🚀
Signed-off-by: Vishu Bhatnagar <vishu.bhatnagar@ibm.com>
3eb8aa6 to
6cf2f1f
Compare
Use the handwritten compatibility contract from #219 without changing
openapi.json. Add the shared React foundation for stored-token connection validation: the exact-gateway API helper, cancellable per-gateway state, strict stage-result checks, reusable result panel, and localized error guidance in English, Spanish, and Portuguese. Preserve the structured OAuth callback fields and cover BFF forwarding.Part 2 of the frontend split for IBM/mcp-context-forge#5722. Based on #219 in native GitHub stack #223. This PR adds the reusable implementation; page-specific controls follow in the MCP Servers and Catalog PRs.
Validation: production build, ESLint, and full frontend coverage suite. API, hook, panel, callback, and locale tests cover compatibility, failures, cancellation, stale responses, and permission loss. The BFF callback regression suite passed (6 tests).
Native GitHub stack: #223, rooted at
main.main.#218 contains the independent E2E typing fix and can merge separately. These PRs replace #216.