Adds a minimal RFC 8414 + RFC 6749 client_credentials flow so claude.ai's custom-MCP integration (no static-Bearer field in the UI) can exchange a client_id + client_secret pair for the existing BRAIN_MCP_TOKEN and use it as a Bearer on /mcp. No JWTs, no refresh, no expiry — the rest of the auth middleware is unchanged. New package ingestion/internal/oauth: - MetadataHandler(issuer): serves /.well-known/oauth-authorization-server with grant_types=[client_credentials] and both token_endpoint_auth_methods (post + basic). - TokenHandler(cfg): serves /oauth/token. Validates client_id and client_secret via constant-time compare; returns BRAIN_MCP_TOKEN as access_token. RFC 6749 §5.2 error JSON on bad grant / bad creds. Wiring in cmd/server/main.go: opt-in by setting both OAUTH_CLIENT_ID and OAUTH_CLIENT_SECRET. Setting only one is misconfiguration → exit 1. Mounts both endpoints with no auth; MCP_RESOURCE_URL supplies the issuer. Also pivots issue #8's vector backend from Qdrant to pgvector (see DECISIONS.md 2026-05-18) — Qdrant was never deployed and postgres18 with pgvector already runs as the project default; supersedes 2026-04-08 for this use case. Tests cover post-auth, basic-auth, wrong secret, bad grant, GET rejection, malformed Basic header, and Basic without colon. Closes hyperguild#5.
42 lines
1.5 KiB
Go
42 lines
1.5 KiB
Go
package oauth_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/mathiasbq/hyperguild/ingestion/internal/oauth"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestMetadataHandler_ReturnsJSON(t *testing.T) {
|
|
h := oauth.MetadataHandler("https://brain-mcp.d-ma.be")
|
|
req := httptest.NewRequest(http.MethodGet, "/.well-known/oauth-authorization-server", nil)
|
|
rr := httptest.NewRecorder()
|
|
h.ServeHTTP(rr, req)
|
|
|
|
assert.Equal(t, http.StatusOK, rr.Code)
|
|
assert.Equal(t, "application/json", rr.Header().Get("Content-Type"))
|
|
|
|
var body map[string]any
|
|
require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &body))
|
|
assert.Equal(t, "https://brain-mcp.d-ma.be", body["issuer"])
|
|
assert.Equal(t, "https://brain-mcp.d-ma.be/oauth/token", body["token_endpoint"])
|
|
assert.ElementsMatch(t, []any{"client_credentials"}, body["grant_types_supported"])
|
|
assert.ElementsMatch(t,
|
|
[]any{"client_secret_post", "client_secret_basic"},
|
|
body["token_endpoint_auth_methods_supported"])
|
|
}
|
|
|
|
func TestMetadataHandler_StripsTrailingSlashFromIssuer(t *testing.T) {
|
|
h := oauth.MetadataHandler("https://brain-mcp.d-ma.be/")
|
|
rr := httptest.NewRecorder()
|
|
h.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/.well-known/oauth-authorization-server", nil))
|
|
var body map[string]any
|
|
require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &body))
|
|
assert.Equal(t, "https://brain-mcp.d-ma.be", body["issuer"])
|
|
assert.Equal(t, "https://brain-mcp.d-ma.be/oauth/token", body["token_endpoint"])
|
|
}
|