Support HTTP Basic auth in API-key connections

Cursor's Admin API authenticates with the admin key as the HTTP
Basic auth username (empty password) and rejects Bearer tokens.
The API-key connection previously supported only Bearer and a
custom header (Anthropic's x-api-key); add a Basic-auth mode
selected by Registration.APIKeyBasicAuth, and reject providers
that set both it and APIKeyHeader.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
This commit is contained in:
Aurélien Sibiril
2026-05-28 23:44:28 +02:00
parent 72c35a9967
commit 0149ca4f55
6 changed files with 179 additions and 2 deletions

View File

@@ -34,6 +34,13 @@ type APIKeyConnection struct {
// It is populated from the provider Registration at connector
// creation time.
Header string `json:"header,omitempty"`
// BasicAuth, when true, presents the API key as the username of an
// HTTP Basic credential with an empty password (`Authorization:
// Basic base64(<key>:)`) — required by providers such as Cursor
// whose Admin API documents Basic auth and rejects Bearer tokens.
// It is mutually exclusive with Header and is populated from the
// provider Registration at connector creation time.
BasicAuth bool `json:"basic_auth,omitempty"`
}
var _ Connection = (*APIKeyConnection)(nil)
@@ -49,6 +56,15 @@ func (c *APIKeyConnection) Scopes() []string {
func (c *APIKeyConnection) Client(ctx context.Context) (*http.Client, error) {
underlying := httpclient.DefaultPooledTransport(httpclient.WithSSRFProtection())
if c.BasicAuth {
return &http.Client{
Transport: &basicAuthTransport{
username: c.APIKey,
underlying: underlying,
},
}, nil
}
if c.Header != "" {
return &http.Client{
Transport: &apiKeyHeaderTransport{
@@ -86,6 +102,22 @@ func (t *apiKeyHeaderTransport) RoundTrip(req *http.Request) (*http.Response, er
return t.underlying.RoundTrip(req2)
}
// basicAuthTransport presents the API key as the username of an HTTP
// Basic credential with an empty password. Providers such as Cursor
// document `-u <key>:` Basic auth for their Admin API and reject Bearer
// tokens, so neither oauth2Transport nor apiKeyHeaderTransport fits.
type basicAuthTransport struct {
username string
underlying http.RoundTripper
}
func (t *basicAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) {
req2 := req.Clone(req.Context())
req2.SetBasicAuth(t.username, "")
return t.underlying.RoundTrip(req2)
}
func (c APIKeyConnection) MarshalJSON() ([]byte, error) {
type Alias APIKeyConnection