Wire non-interactive registry auth for serve mode#4111
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #4111 +/- ##
==========================================
+ Coverage 68.82% 69.08% +0.26%
==========================================
Files 468 470 +2
Lines 47087 47351 +264
==========================================
+ Hits 32407 32713 +306
- Misses 11996 12085 +89
+ Partials 2684 2553 -131 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
jhrozek
left a comment
There was a problem hiding this comment.
Automated review from Claude Code. 14 findings total (3 HIGH, 6 MEDIUM, 5 LOW/INFO). 4 HIGH/MEDIUM findings are pre-existing from PR #3908 and should be tracked separately. 6 inline comments below on code introduced in this PR.
Pre-existing issues to track separately (from PR #3908):
- HIGH: Refresh token rotation not persisted on cache restore path (
oauth_token_source.go:94) - HIGH: Data race between
ResetDefaultProviderandGetDefaultProviderWithConfig(factory.go) - MEDIUM:
updateConfigTokenRefbypasses injected config provider (oauth_token_source.go:213) - MEDIUM: Panic on empty
baseURLinapi.NewClient(client.go:111)
MEDIUM (not inline — outside diff hunk): resolveTokenSource in factory.go:144 passes cfg.RegistryApiUrl directly, but auth.Login uses registryURLFromConfig(cfg) which falls back to cfg.RegistryUrl. Since the URL feeds DeriveSecretKey, a mismatch means Login could store the token under a different key than factory looks up.
🤖 Generated with Claude Code
f8aebd1 to
4713fe7
Compare
Wire non-interactive registry auth for serve mode so that browser-based OAuth flows are never triggered from the API server. When registry auth is configured but tokens are missing or expired, the API returns a structured 503 response with code "registry_auth_required" instead of hanging on a browser redirect. Key changes: - Add WithInteractive(false) provider option for headless contexts - Add GetNonInteractiveProviderWithConfig for serve mode - Add auth status fields (auth_status, auth_type) to registry API responses - Add POST /auth/login and POST /auth/logout API endpoints - Add auth fields to PUT registry endpoint with offline_access scope - Return structured 503 errors when registry auth is required - Wrap validation probe 401 with ErrRegistryAuthRequired Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
51cc2d7 to
b3d9fc1
Compare
…oolhive into feat/registry-auth-serve-mode
|
@jhrozek I might need another approval |
Clients like Studio need to display the configured issuer and client_id without reading the config file directly. Add an auth_config field to the GET /registry and GET /registry/default responses that surfaces the non-secret OAuth configuration (issuer, client_id, audience, scopes). The field is omitted when no auth is configured. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Chris Burns <29541485+ChrisJBurns@users.noreply.github.com>
* Add serve-mode registry auth to thv serve API Wire non-interactive registry auth for serve mode so that browser-based OAuth flows are never triggered from the API server. When registry auth is configured but tokens are missing or expired, the API returns a structured 503 response with code "registry_auth_required" instead of hanging on a browser redirect. Key changes: - Add WithInteractive(false) provider option for headless contexts - Add GetNonInteractiveProviderWithConfig for serve mode - Add auth status fields (auth_status, auth_type) to registry API responses - Add POST /auth/login and POST /auth/logout API endpoints - Add auth fields to PUT registry endpoint with offline_access scope - Return structured 503 errors when registry auth is required - Wrap validation probe 401 with ErrRegistryAuthRequired Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs Signed-off-by: Chris Burns <29541485+ChrisJBurns@users.noreply.github.com> * Return OAuth public config in registry GET responses Clients like Studio need to display the configured issuer and client_id without reading the config file directly. Add an auth_config field to the GET /registry and GET /registry/default responses that surfaces the non-secret OAuth configuration (issuer, client_id, audience, scopes). The field is omitted when no auth is configured. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * updates docs Signed-off-by: Chris Burns <29541485+ChrisJBurns@users.noreply.github.com> --------- Signed-off-by: Chris Burns <29541485+ChrisJBurns@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Summary
Why: When
thv serveruns with OAuth-authenticated registries, it must never open a browser for OAuth flows — it's a headless API server. Desktop clients (ToolHive Studio) need: (1) machine-readable auth status to show the correct UI state, (2) structured error responses when auth is missing so they can prompt the user to authenticate, and (3) API endpoints to trigger login/logout without needing a terminal.What: Adds serve-mode registry auth support to the
thv serveAPI:WithInteractive(false)suppresses browser OAuth flows in serve modeauth_status(none/configured/authenticated) andauth_typefields in all registry GET responses{"code": "registry_auth_required", "message": "..."}when tokens are missing, instead of generic 500sRegistryHTTPErrorwithUnwrap()for 401/403 detection from upstream registries, withLimitReaderon error bodiesErrRegistryAuthRequiredand surfaced as 503s to clientsPOST /api/v1beta/registry/auth/login— triggers interactive OAuth flow (desktop-only, opens browser)POST /api/v1beta/registry/auth/logout— clears cached OAuth tokens and registry cachePUT /api/v1beta/registry/defaultnow accepts an optionalauthobject withissuer,client_id,audience, andscopesfields for configuring registry OAuthGetAuthStatus()method consolidates auth state inspection for API responsesType of change
Test plan
task test)task lint-fix)Changes
pkg/registry/api/client.goErrRegistryUnauthorized,RegistryHTTPErrortype withUnwrap()for 401/403. Replacefmt.Errorfwith typed errors andLimitReaderinGetServer,fetchServersPage,SearchServers.pkg/registry/factory.goProviderOption/WithInteractive(), thread opts throughGetDefaultProviderWithConfigandNewRegistryProvider.pkg/registry/provider_api.goGetRegistry()withErrRegistryAuthRequiredfor structured 503 responses.pkg/registry/auth_manager.goGetAuthStatus()method andAuthStatusNone/AuthStatusConfigured/AuthStatusAuthenticatedconstants.pkg/registry/auth_manager_test.goGetAuthStatus().pkg/api/v1/registry.goRegistryRoutes.serveMode,NewRegistryRoutesForServe(),RegistryRouter(serveMode bool). Structured 503 errors:writeRegistryAuthRequiredError(),isRegistryAuthError(). Auth status:resolveAuthStatus(),auth_status/auth_typefields inregistryInfoandgetRegistryResponse. API endpoints:POST /auth/login,POST /auth/logout. OAuth config:processAuthUpdate(),UpdateRegistryAuthRequest. Auth error handling inlistRegistries,getRegistry,listServers,updateRegistry.pkg/api/server.goserveMode: truetoRegistryRouter().docs/server/*Does this introduce a user-facing change?
Yes.
GET /api/v1beta/registryandGET /api/v1beta/registry/default) now includeauth_statusandauth_typefields.thv servecannot authenticate to a registry (no cached tokens), registry endpoints return HTTP 503 with{"code": "registry_auth_required"}instead of a generic 500.POST /api/v1beta/registry/auth/loginandPOST /api/v1beta/registry/auth/logoutendpoints (serve mode only) allow desktop clients to trigger OAuth login/logout via the API.PUT /api/v1beta/registry/defaultnow accepts anauthobject to configure OAuth settings via the API.Special notes for reviewers
thv serveAPI — the problem is thatthv serveitself lacks a registry credential. 503 correctly signals a server-side dependency issue, not a client auth failure.auth_statuslimitations: Cannot detect "expired" from config alone (would require calling the token source). We return "authenticated" when a cached token ref exists and rely on Studio handling subsequent 401s as a signal to re-login./auth/loginendpoint opens the user's default browser for the OAuth flow. This is intentional for desktop-only serve mode (Studio runningthv servelocally). A follow-up could return a redirect URL instead for remote/container environments.omitemptyon auth fields:auth_statusandauth_typeintentionally omitomitemptyso clients always receive the fields, even when the value is"none"or empty string.Generated with Claude Code