Files
summercms/modules/surf/pier_on.go
Jakub Zych 24cf6cd931 fix(pier): end only the revoked token's or client's MCP sessions and log the client address (B-WR-04, B-IN-04)
- pact.MCPSubjectChanged and pier.Event gain TokenID and ClientID; a
  narrowed revoked event closes only matching sessions and stateless servers
- cabana fires the admin token revoke with the token row id
- towel.WithClientIP/ClientIP; pier puts the address (surf applies
  http.trusted_proxies) on the resolve context and ResolveBackendToken logs it
2026-10-23 16:21:37 +02:00

79 lines
2.6 KiB
Go

//go:build summercms_mcp
package surf
import (
"fmt"
"net/http"
"net/netip"
"strings"
"git.golem15.com/golem15/summercms/modules/backpack"
"git.golem15.com/golem15/summercms/modules/pact"
"git.golem15.com/golem15/summercms/modules/party"
"git.golem15.com/golem15/summercms/modules/pier"
)
// Binaries built with the summercms_mcp tag (MCPBuildTag) link the MCP
// module: this file sets the mount hook BuildRouter calls when mcp.enabled
// is true, with an adapter that gives pier the route table, the compiled
// handler lookup and the two mount kinds without pier importing surf.
func init() {
mountPier = func(r *Router, app *backpack.App, plugins []party.Plugin) error {
return pier.Mount(pierHost{r: r, trusted: TrustedProxies(app.Config)}, app, plugins)
}
}
// pierHost adapts a Router to pier.Host. trusted holds http.trusted_proxies
// for ClientIP.
type pierHost struct {
r *Router
trusted []netip.Prefix
}
// ClientIP answers the request's client address with the application's
// trusted proxies applied, the same rule the throttle middleware uses; pier
// puts it on the token resolvers' context.
func (h pierHost) ClientIP(req *http.Request) string {
return ClientIP(req, h.trusted)
}
func (h pierHost) Routes() []pier.RouteInfo {
routes := h.r.Routes()
out := make([]pier.RouteInfo, len(routes))
for i, ri := range routes {
out[i] = pier.RouteInfo{Method: ri.Method, Pattern: ri.Pattern, PluginID: ri.PluginID, Middleware: ri.Middleware}
}
return out
}
func (h pierHost) Handler(method, pattern string) (http.Handler, bool) {
return h.r.Handler(method, pattern)
}
func (h pierHost) MountStream(method, path string, handler http.Handler, middleware ...string) error {
h.r.BindPlugin(pier.PluginID)
return h.r.MountStream(method, path, handler, middleware...)
}
// MountJSON registers a plain GET route (no middleware) under the pier
// pseudo-plugin; only GET is supported.
func (h pierHost) MountJSON(method, path string, handler http.Handler) error {
if strings.ToUpper(strings.TrimSpace(method)) != http.MethodGet {
return fmt.Errorf("surf: pier may mount JSON routes with GET only, got %q", method)
}
if owner, ok := h.Owner(http.MethodGet, path); ok {
return fmt.Errorf("surf: route GET %s (plugin %q) collides with a route the MCP module mounts", path, owner)
}
h.r.BindPlugin(pier.PluginID)
h.r.GroupRaw("", nil, func(g pact.Router) {
g.Get(path, handler.ServeHTTP)
})
return nil
}
func (h pierHost) Owner(method, path string) (string, bool) {
owner, ok := h.r.seen[strings.ToUpper(strings.TrimSpace(method))+" "+path]
return owner, ok
}