mirror of
https://gh-proxy.org/https://github.com/klinsmaya/multica.git
synced 2026-11-08 01:16:45 +08:00
* feat(api): establish public v1 contract foundation Co-authored-by: multica-agent <[email protected]> * fix(plugins): close public API contract gaps Co-authored-by: multica-agent <[email protected]> * fix(issues): enforce revision guard during update Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-agent <[email protected]>
48 lines
1.7 KiB
Go
48 lines
1.7 KiB
Go
package middleware
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
|
|
publicapiv1 "github.com/multica-ai/multica/server/pkg/publicapi/v1"
|
|
)
|
|
|
|
// PluginBearerOnly keeps the public Action API on a machine-credential trust
|
|
// boundary. Browser sessions use the separate, session-authenticated bridge
|
|
// route; the public /v1 contract accepts only installation and callback tokens.
|
|
//
|
|
// Prefix matching is only routing. The Action handler still resolves the token
|
|
// against its store and rejects an invalid or expired credential.
|
|
func PluginBearerOnly(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if !IsPluginBearerToken(BearerToken(r)) {
|
|
publicapiv1.WriteProblem(w, r, http.StatusUnauthorized, "plugin_bearer_required", "plugin bearer token required")
|
|
return
|
|
}
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
// BearerToken pulls the raw credential out of an Authorization header.
|
|
func BearerToken(r *http.Request) string {
|
|
header := strings.TrimSpace(r.Header.Get("Authorization"))
|
|
if header == "" {
|
|
return ""
|
|
}
|
|
const prefix = "Bearer "
|
|
if len(header) <= len(prefix) || !strings.EqualFold(header[:len(prefix)], prefix) {
|
|
return ""
|
|
}
|
|
return strings.TrimSpace(header[len(prefix):])
|
|
}
|
|
|
|
// IsPluginBearerToken reports whether a credential is one of ours to resolve.
|
|
//
|
|
// Prefix-matched rather than validated: this only decides which code path gets
|
|
// to look at the token, and an invalid token routed here is refused by the
|
|
// handler a moment later. Deciding by prefix keeps a plugin token from being
|
|
// tried against the PAT cache and a PAT from being tried against installations.
|
|
func IsPluginBearerToken(token string) bool {
|
|
return strings.HasPrefix(token, "mpi_") || strings.HasPrefix(token, "mpc_")
|
|
}
|