Files
ResolveSpec/pkg/resolvemcp/guard.go
T
Hein ad2f54693f feat(resolvemcp): require authentication on MCP endpoints and enforce model rules on writes
Guard() rejects unauthenticated callers (no guest/optional mode); Setup*/New* helpers take a
SecurityList and have explicit *Unauthenticated variants. Model rules now reach the security
hooks, create checks CanCreate (security.CheckModelCreateAllowed), create/update validate keys
against the model's writable columns, update sets only given keys (NULL allowed), update and
delete go through row security via a new BeforeScan hook, and the annotation tool is opt-in
(Config.EnableAnnotations) and runs BeforeHandle.
2026-10-01 13:31:13 +02:00

53 lines
2.1 KiB
Go

package resolvemcp
import (
"net/http"
"github.com/bitechdev/ResolveSpec/pkg/logger"
"github.com/bitechdev/ResolveSpec/pkg/security"
)
// Guard returns middleware that requires an authenticated caller on every request.
//
// The security list's provider decides which credentials are accepted: build it from a
// security.ChainAuthenticator over an OAuth bearer token, a session token (header or cookie)
// and an API key authenticator. The authenticated security.UserContext is placed in the request
// context, which the MCP transports pass on to every tool call, so rules, row security and
// OnTxBegin apply to that caller.
//
// Unlike security.NewAuthMiddleware this guard has no guest or optional mode: it ignores
// security.SkipAuth / security.OptionalAuth markers on the request context, and fails closed
// (500) when no provider is configured.
func Guard(securityList *security.SecurityList) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
authed := security.NewAuthHandler(securityList, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if uc, ok := security.GetUserContext(r.Context()); !ok || uc == nil {
http.Error(w, "Authentication failed", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
}))
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if securityList == nil {
http.Error(w, "Security provider not configured", http.StatusInternalServerError)
return
}
authed.ServeHTTP(w, r)
})
}
}
// requireGuard reports whether securityList can guard a route. Setup helpers use it to refuse
// to mount an endpoint rather than serve it unauthenticated by mistake.
func requireGuard(fn string, securityList *security.SecurityList) bool {
if securityList == nil || securityList.Provider() == nil {
logger.Error("resolvemcp.%s: no security provider configured; MCP endpoint NOT mounted", fn)
return false
}
return true
}
func warnUnauthenticated(fn string) {
logger.Warn("resolvemcp.%s: serving the MCP endpoint WITHOUT authentication; every caller can read and write all registered models", fn)
}