78 lines
3.0 KiB
Go
78 lines
3.0 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
|
|
chassisauth "git.d-ma.be/mathias/mcp-chassis/auth"
|
|
|
|
"github.com/mathiasbq/hyperguild/ingestion/internal/api"
|
|
)
|
|
|
|
// publicRoutes is the complete set of patterns allowed on the outer mux,
|
|
// outside the REST API's auth (hyperguild#149). Each authenticates itself or is
|
|
// meant to be open: /mcp (bearer/JWT), /capture (bearer/JWT), the webhook
|
|
// (HMAC), /oauth/token (client secret), OAuth metadata and /metrics (open).
|
|
// A REST pattern added here would beat the authenticated catch-all by
|
|
// specificity and ship open, which is why buildMux refuses anything else.
|
|
var publicRoutes = map[string]bool{
|
|
"/mcp": true,
|
|
"POST /capture": true,
|
|
"POST /webhooks/brain-sync": true,
|
|
"GET /.well-known/oauth-protected-resource": true,
|
|
"GET /.well-known/oauth-authorization-server": true,
|
|
"POST /oauth/token": true,
|
|
"GET /metrics": true,
|
|
}
|
|
|
|
// restAuth is the credential the REST API checks: the same as /mcp's.
|
|
type restAuth struct {
|
|
token string
|
|
validator *chassisauth.JWTValidator
|
|
realm string
|
|
resourceMetadataURL string
|
|
}
|
|
|
|
// buildMux is the server's whole router. The REST API is mounted at "/"
|
|
// behind bearer auth that buildMux constructs itself, so a caller cannot pass
|
|
// a no-op; public handlers are registered as given, and only for patterns on
|
|
// the publicRoutes allowlist. It returns an http.Handler, not the
|
|
// *ServeMux, so nothing can register a route on the result afterwards and
|
|
// bypass the allowlist.
|
|
func buildMux(auth restAuth, h *api.Handler, public map[string]http.Handler) (http.Handler, error) {
|
|
if auth.token == "" {
|
|
return nil, errors.New("buildMux: REST auth needs a token")
|
|
}
|
|
mux := http.NewServeMux()
|
|
for pattern, handler := range public {
|
|
if !publicRoutes[pattern] {
|
|
return nil, fmt.Errorf("buildMux: %q is not an allowed public route", pattern)
|
|
}
|
|
mux.Handle(pattern, handler)
|
|
}
|
|
mountREST(mux, h, func(next http.Handler) http.Handler {
|
|
return chassisauth.BearerMiddleware(auth.token, auth.validator, auth.realm, auth.resourceMetadataURL, next)
|
|
})
|
|
return mux, nil
|
|
}
|
|
|
|
// mountREST registers the REST API on its own mux and mounts it at "/" behind
|
|
// auth: anything not registered as public lands here and is authenticated,
|
|
// including routes added later.
|
|
func mountREST(mux *http.ServeMux, h *api.Handler, auth func(http.Handler) http.Handler) {
|
|
rest := http.NewServeMux()
|
|
rest.HandleFunc("POST /query", h.Query)
|
|
rest.HandleFunc("POST /write", h.Write)
|
|
rest.HandleFunc("POST /index", h.Index)
|
|
rest.HandleFunc("POST /ingest", h.Ingest)
|
|
rest.HandleFunc("POST /ingest-path", h.IngestPath)
|
|
rest.HandleFunc("POST /ingest-raw", h.IngestRaw)
|
|
rest.HandleFunc("POST /backfill-refs", h.BackfillRefs)
|
|
rest.HandleFunc("GET /pending", h.Pending)
|
|
rest.HandleFunc("POST /promote", h.Promote)
|
|
rest.HandleFunc("POST /backfill-embeddings", h.BackfillEmbeddings)
|
|
rest.HandleFunc("GET /pass-rate", h.PassRate)
|
|
mux.Handle("/", auth(rest))
|
|
}
|