Skip to content

feat(registry): handle authentication for registry server api#1783

Merged
peppescg merged 27 commits into
mainfrom
issues/1760
Mar 30, 2026
Merged

feat(registry): handle authentication for registry server api#1783
peppescg merged 27 commits into
mainfrom
issues/1760

Conversation

@peppescg
Copy link
Copy Markdown
Collaborator

@peppescg peppescg commented Mar 23, 2026

Summary

Kapture.2026-03-26.at.19.22.38.mp4

Adds OAuth configuration support for Registry Server API and handles structured backend errors (registry_auth_required, registry_unavailable) with actionable UI feedback.

OAuth fields for Registry Server API

  • Show Client ID and Issuer URL fields when Registry Server API is selected
  • Submit both as the auth object in PUT /api/v1beta/registry/default
  • Pre-populate saved values when the user opens settings
  • Login/logout flow after saving api_url with auth credentials

Error handling — registry_auth_required (HTTP 401)

  • Detect code: "registry_auth_required" from GET /api/v1beta/registry
  • Auto-select Registry Server API type and show field-level errors:
    • Empty auth fields → red borders + general OIDC message in the OAuth box
    • Populated fields → specific error on Client ID field
  • Dedicated full-page error with "Resolve issues" link to settings

Error handling — registry_unavailable (HTTP 503)

  • Detect code: "registry_unavailable" from GET /api/v1beta/registry
  • Auto-select Registry Server API type and show user-friendly error on source field
  • Pre-fill the source field with the misconfigured URL extracted from the error
  • Dedicated full-page error with "Resolve issues" link to settings

Submit error handling

  • OIDC discovery failure → error on Issuer URL field
  • Auth login failure → error on Client ID field, logout cleanup
  • Missing auth fields → error on both Client ID and Issuer URL
  • Source URL preserved after login failure (keepDirtyValues)

Refactoring

  • Extract useRegistryData and useRegistryForm hooks from registry tab
  • Extract applySubmitFieldErrors with switch-based error mapping
  • Replace string literals with REGISTRY_FORM_TYPE constants
  • Simplify errorMsg toast callback with set-based known message lookup
  • Extract OAuthField component and OAUTH_FIELDS config
  • Deduplicate onValueChange logic in registry type dropdown
  • Extract complex inline conditions into named variables

Test plan

  • Select Registry Server API → Client ID and Issuer URL fields appear
  • Save with OAuth fields → PUT includes auth object
  • Save without OAuth fields → PUT omits auth, no login attempt
  • GET returns registry_auth_required with no config → red borders + OIDC message
  • GET returns registry_auth_required with existing config → error on Client ID
  • GET returns registry_unavailable → error on source field with pre-filled URL
  • Login failure → error on Client ID, logout called, source URL preserved
  • OIDC discovery failure → error on Issuer URL field
  • Missing auth fields → error on both fields
  • Switching registry type clears previous errors
  • Invalid API URL / Issuer URL format → validation errors
  • Full-page error for auth_required and unavailable with "Resolve issues" button

Closes #1760

@peppescg peppescg self-assigned this Mar 23, 2026
@peppescg peppescg linked an issue Mar 23, 2026 that may be closed by this pull request
4 tasks
@peppescg
Copy link
Copy Markdown
Collaborator Author

@claude succinct review

@claude
Copy link
Copy Markdown

claude Bot commented Mar 24, 2026

Claude finished @peppescg's task in 1m 3s —— View job


Code Review

  • Gathering context
  • Reading key files
  • Review complete

Overall this is well-structured — the hook extraction is clean and the error-handling logic is clearly separated. A few things worth addressing:

Bugs / Correctness

  1. buildRegistryAuth called twiceuse-registry-update-mutation.ts:119-122 computes auth a second time after buildRegistryBody already called it internally. The result is only used to gate the login call, so it's harmless but redundant. Consider reusing the value from buildRegistryBody or extracting it once.

  2. allow_private_ip: true hardcodeduse-registry-update-mutation.ts:109. This is silently sent for every api_url save. If the existing value is false, saving will flip it. Should this preserve the current value or be a user-controlled field? Fix this →

  3. Unsafe auth_config castutils.ts:40-41. auth_config is cast as Record<string, string> with no runtime check. If the shape ever differs, client_id/issuer_url will silently be undefined without surfacing an error. Worth a type guard or using optional chaining on a properly typed field.

UX / State

  1. formSource priority is surprisinguse-registry-form.ts:71-74:

    defaultRegistry?.source ?? (isUnavailableError ? registryUnavailableUrl : undefined) ?? ''

    If the registry has a saved source and an unavailable error, the saved source wins. But the error URL extracted from the 503 message may differ from the stored source — the user might not see what's actually broken. Consider showing registryUnavailableUrl when isUnavailableError is true regardless of whether a stored source exists.

  2. Empty-string error messagesuse-registry-form.ts:97-99 sets source, client_id, and issuer_url errors with message: '' to trigger red borders without text. This works but feels fragile; a future UI component that only renders the border conditionally on !!error.message would silently regress. Consider a sentinel message like ' ' (non-breaking space) or a dedicated hasError prop.

Schema

  1. api_url accepts HTTPschema.ts:67-82 only validates that the URL parses, not that it's HTTPS. The url type enforces HTTPS; consistency would prevent accidental plaintext credential transmission. Fix this →

Minor

  1. delay(500) in mutationuse-registry-update-mutation.ts:124. Artificial delay in the happy path feels like a debug leftover. Is this intentional?

  2. resolveRegistryListLoadErrorMessageregistry-list-error.ts:80-91 takes registryType from the form but the function is also imported in registry-api-oauth-fields.tsx which already knows the type is api_url (it returns null otherwise). The function's fallback branch for non-api_url types is dead code in its only call site.

peppescg and others added 14 commits March 26, 2026 16:56
- Add client_id/issuer_url OAuth fields for Registry Server API type
- Add login/logout flow when saving api_url with auth credentials
- Show registry_auth_required error with field-level feedback:
  - Empty auth fields: red borders + general OIDC message
  - Populated fields: REGISTRY_WRONG_AUTH_TOAST on client_id
- Preserve source URL after login failure (keepDirtyValues)
- Clear form errors on registry type dropdown change
- Refactor into useRegistryData + useRegistryForm hooks
- Extract REGISTRY_FORM_TYPE constant for type-safe comparisons
- Add OIDC_DISCOVERY_PATTERN and error constants to registry-list-error

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Test OIDC discovery failure shows issuer_url field error
- Test dropdown type change clears previous form errors
- Test source URL is preserved after login failure (keepDirtyValues)
- Test issuer URL format validation
- Test registry_auth_required with existing auth config shows
  client_id field error instead of general OIDC box message

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…onstants and extract OAuthField

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…igning in.../Saving... on load

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Display a dedicated error page and form-level error when the backend
returns a 503 with code `registry_unavailable` (misconfigured API URL).
Pre-fill the source field with the URL extracted from the error so the
user can correct it directly. Refactor submit error handling into a
switch-based helper function for readability.

Made-with: Cursor
Remove re-exports of toast constants from use-registry-update-mutation
(now imported directly from registry-list-error) and un-export
REGISTRY_LIST_LOAD_FALLBACK_MESSAGE which is only used internally.

Made-with: Cursor
- Deduplicate onValueChange logic in registry-type-field
- Simplify errorMsg callback with set-based known toast lookup
- Extract complex inline condition into named showSourceError variable
- Remove unused registryUnavailableMessage from useRegistryForm return

Made-with: Cursor
Pass auth from mutationFn into buildRegistryBody instead of calling
buildRegistryAuth in both places independently.

Made-with: Cursor
…ed types

auth_config is already typed as GithubComStacklokToolhivePkgRegistryOAuthPublicConfig
by the generated OpenAPI types — the Record<string, string> cast bypassed
compile-time safety for field renames.

Made-with: Cursor
When registry_unavailable fires, React Query may still hold the old
cached source. Prioritize the URL extracted from the 503 error so the
user sees the actual broken URL they need to fix.

Made-with: Cursor
…ages

- Show OIDC box message without field-level blame when auth config
  exists but registry_auth_required fires (the GET error does not mean
  credentials are wrong — login may not have been attempted yet)
- Use field-specific messages for missing auth fields: "Client ID is
  required" / "Issuer URL is required" instead of generic "Both..."
- Clear stale field errors at the start of the error effect to prevent
  unavailable message leaking into auth_required state
- Simplify hasRegistryError to hasError && !isUnavailableError
- Update auth_required UI message to clarify the registry requires
  authentication

Made-with: Cursor
…and field errors

Covers registry_unavailable source error + URL pre-fill, auth_required
with existing OIDC config showing only the box message, and per-field
validation messages on submit with missing auth fields.

Made-with: Cursor
…rection

Merge getErrorCode/getErrorMessage into a single getErrorField accessor.
Replace getRegistryUnavailableMessage getter with an exported constant
used directly in the form hook, removing a data-hook return value and
a redundant effect dependency.

Made-with: Cursor
…istry button

Detect when thv exits due to a registry authentication error and
redirect the user to Settings > Registry with a persistent error toast.

- Add getToolhiveStatus IPC exposing exitReason from thv stderr
- Extract shared ToolhiveStatus types to common/types
- Redirect to /settings?tab=registry on registry-auth-required
- Show persistent error toast in registry form when auth error detected
- Add "Reset Registry" button to restore default registry config
- Use mutation variables to derive isResetting state without extra useState
- Guard setupSecretProvider in loader when thv is not running

Made-with: Cursor
More descriptive name that clearly indicates a process-level error
rather than a normal exit reason. Rename exitReason field to
processError across common types, main, preload, and renderer.

Made-with: Cursor
…efaults

- Rename registry-list-error.ts to registry-errors.ts for clarity
- Move REGISTRY_UNAVAILABLE_UI_MESSAGE to registry-errors.ts (was
  duplicated in registry-error.tsx and its test)
- Extract inline form defaults to EMPTY_REGISTRY_DEFAULTS constant
- Extract onValueChange logic to restoreFieldsForType function

Made-with: Cursor
…et button

- Merge two useEffects into one that reacts to both error state and
  type changes, clearing and recomputing form errors in a single pass
- Flatten nested if in auth error handling with hasMissingCredentials
- Simplify reset button: secondary variant, rounded, no icon

Made-with: Cursor
- Rename registry-errors.ts to registry-errors-message.ts to avoid
  confusion with the registry-error.tsx component
- Remove useEffect that imperatively synced query errors into form
  state via form.setError/clearErrors
- Handle unavailable error declaratively via isUnavailableError prop
  passed to RegistrySourceField
- Clear form errors on type change directly in RegistryTypeField
  onValueChange handler
- Move auth-required toast to __root.tsx redirect block (event-driven)
  and dismiss it in onReset/onSubmit handlers
- Invalidate registry query on successful api_url login so error state
  clears and form updates correctly after authentication

Made-with: Cursor
@peppescg peppescg marked this pull request as ready for review March 27, 2026 10:10
Copilot AI review requested due to automatic review settings March 27, 2026 10:10
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds OAuth configuration and improved error handling for Registry Server API usage in the Registry Settings UI, including startup redirection when ToolHive indicates registry authentication is required.

Changes:

  • Add Registry Server API OAuth fields (Client ID, Issuer URL), submit as auth, and trigger login/logout flows.
  • Handle structured backend errors (registry_auth_required, registry_unavailable) with targeted UI messaging (settings + full-page error state).
  • Refactor registry settings logic into useRegistryData / useRegistryForm, plus expanded MSW fixtures and test coverage.

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
renderer/src/routes/__root.tsx Redirects to registry settings + persistent toast when ToolHive reports registry auth required at startup.
renderer/src/routes/(registry)/registry.tsx Passes route error into RegistryError for structured rendering.
renderer/src/common/mocks/fixtures/registry_auth_logout/post.ts Adds MSW fixture for registry auth logout endpoint.
renderer/src/common/mocks/fixtures/registry_auth_login/post.ts Adds MSW fixture for registry auth login endpoint (+ error scenario).
renderer/src/common/mocks/fixtures/registry/get.ts Adds MSW fixture for GET registry list endpoint (+ error scenario).
renderer/src/common/hooks/use-toast-mutation.ts Extends toast mutation hook to support functional loading/error messages.
renderer/src/common/components/settings/tabs/tests/registry-tab.test.tsx Adds extensive tests for OAuth fields, auth/unavailable errors, and submission error mappings.
renderer/src/common/components/settings/registry/utils.ts Adds REGISTRY_FORM_TYPE constants and helper to extract auth config from registry info.
renderer/src/common/components/settings/registry/use-registry-update-mutation.ts Centralizes PUT + auth login/logout flow, cache updates, and toast/error mapping.
renderer/src/common/components/settings/registry/use-registry-form.ts Centralizes form state, submit/reset handlers, and field-error application.
renderer/src/common/components/settings/registry/use-registry-data.ts Centralizes registry query + update mutation state and structured error detection.
renderer/src/common/components/settings/registry/schema.ts Adds client/issuer fields + issuer URL validation; improves required-source validation copy for API URL.
renderer/src/common/components/settings/registry/registry-type-field.tsx Clears errors and restores/clears dependent fields when switching registry type.
renderer/src/common/components/settings/registry/registry-tab.tsx Refactors tab to use extracted hooks and pass richer props to RegistryForm.
renderer/src/common/components/settings/registry/registry-source-field.tsx Improves per-error messaging (auth-required vs unavailable) and when to mark the source field errored.
renderer/src/common/components/settings/registry/registry-form.tsx Adds OAuth fields section + reset button + improved submit labels/spinner behavior.
renderer/src/common/components/settings/registry/registry-errors-message.ts Adds shared constants and helpers for structured registry errors and user-facing copy.
renderer/src/common/components/settings/registry/registry-api-oauth-fields.tsx New OAuth fields component rendered only for Registry Server API type.
renderer/src/common/components/error/registry-error.tsx Renders dedicated full-page states for auth-required/unavailable registry errors.
renderer/src/common/components/error/tests/registry-error.test.tsx Adds tests for the new full-page registry error variants.
preload/src/api/toolhive.ts Adds getToolhiveStatus() to the renderer-facing Electron API.
main/src/toolhive-manager.ts Tracks a processError for registry-auth-required based on ToolHive stderr output.
main/src/ipc-handlers/toolhive.ts Exposes get-toolhive-status IPC handler.
common/types/toolhive-status.ts Adds shared ToolhiveStatus / ToolhiveProcessError types.

Comment thread renderer/src/common/components/settings/registry/use-registry-form.ts Outdated
Comment thread renderer/src/common/components/settings/registry/registry-errors-message.ts Outdated
…d borders

- Only force form to API_URL type when the failing mutation was
  specifically for api_url, not for unrelated registry types
- Add aria-invalid on empty OAuth fields when registry_auth_required
  fires, restoring the red border visual feedback declaratively
- Fix JSDoc typo: "API Server API" → "Registry Server API"

Made-with: Cursor
When registry_auth_required fires and Client ID / Issuer URL are empty,
display "{label} is required" below each field instead of a silent
FormMessage. Fields with existing values only show the general OAuth
box message without per-field blame.

Made-with: Cursor
samuv
samuv previously approved these changes Mar 27, 2026
Copy link
Copy Markdown
Collaborator

@samuv samuv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn’t test it, but the code looks good. I just added a note about __root, but it’s independent of the main focus of this PR

Comment thread renderer/src/routes/__root.tsx Outdated
…edirect on first detection

Add 'registry-unavailable' process error for "registry login failed" and
"upstream registry unavailable" stderr patterns. The beforeLoad guard now
redirects to Settings > Registry for both registry-auth-required and
registry-unavailable, showing a specific toast per error type.

Also extracts __root.tsx beforeLoad/loader into individual guard files
under routes/root/guards/ and errorComponent into routes/root/root-error.tsx
for improved testability and maintainability.

Made-with: Cursor
Remove registry-unavailable from ToolhiveProcessError, stderr matching,
guard logic, and toast — this requires a backend-side fix instead.

Fix CI failures in setup-secret-provider: add missing async to catch
handler and cast mock queryKey to the generated type.

Made-with: Cursor
The direct cast from [string] to the generated queryKey type is
rejected by stricter CI TypeScript — types don't overlap enough.
Cast through unknown first as recommended by the compiler.

Made-with: Cursor
samuv
samuv previously approved these changes Mar 30, 2026
Copy link
Copy Markdown
Collaborator

@samuv samuv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, thank you for the refactoring

… to settings

Add API-level detection in handleRegistryAuthRedirect: GET /registry now
catches both registry_auth_required and registry_unavailable, redirecting
to Settings > Registry with an appropriate toast message. Centralize the
registry-auth-required constant and extract shared helpers for error
classification and toast message resolution.

Made-with: Cursor
Move the toast.error() call from the beforeLoad guard (where it fired
before the Toaster component was mounted, causing it to be lost during
the redirect cycle) into a useEffect hook in the root component. The
guard now saves the pending toast message in the query client, and
useRegistryErrorToast picks it up after mount.

Made-with: Cursor
Copy link
Copy Markdown
Collaborator

@samuv samuv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@peppescg peppescg merged commit 9285979 into main Mar 30, 2026
16 of 17 checks passed
@peppescg peppescg deleted the issues/1760 branch March 30, 2026 12:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: support registry OAuth configuration in Registry Settings UI

3 participants