Files
gitea-mcp/internal/auth/bearer.go
Mathias Bergqvist 9d08352324
All checks were successful
CD / Lint / Test / Vet (push) Successful in 6s
CD / Build & Import (push) Successful in 11s
CD / Deploy via GitOps (push) Successful in 3s
feat(auth): fall back to GITEA_MCP_DEFAULT_TOKEN when no Bearer header
claude.ai connectors call the server with no Authorization header (confirmed
via request logging). Add a configurable default Gitea PAT so unauthenticated
clients (like claude.ai) can still reach the server.

Claude Code continues to pass per-request PATs; defaultToken="" preserves
the existing strict behaviour when the env var is unset.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 22:22:04 +02:00

56 lines
1.6 KiB
Go

package auth
import (
"context"
"net/http"
"strings"
"time"
)
type tokenKey struct{}
// BearerMiddleware validates the incoming bearer token as a Gitea PAT by
// calling GET /api/v1/user. The validated token is stored in context for
// downstream use by the Gitea client.
//
// defaultToken, if non-empty, is used when no Authorization header is present
// (e.g. claude.ai connectors which do not inject Bearer tokens).
func BearerMiddleware(giteaBaseURL, defaultToken string, next http.Handler) http.Handler {
hc := &http.Client{Timeout: 5 * time.Second}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token, ok := strings.CutPrefix(r.Header.Get("Authorization"), "Bearer ")
if !ok || token == "" {
if defaultToken == "" {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
token = defaultToken
}
req, err := http.NewRequestWithContext(r.Context(), http.MethodGet, giteaBaseURL+"/api/v1/user", nil)
if err != nil {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
req.Header.Set("Authorization", "token "+token)
resp, err := hc.Do(req)
if err != nil || resp.StatusCode != http.StatusOK {
if resp != nil {
_ = resp.Body.Close()
}
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
_ = resp.Body.Close()
ctx := context.WithValue(r.Context(), tokenKey{}, token)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// TokenFromContext returns the validated Gitea PAT stored by BearerMiddleware.
func TokenFromContext(ctx context.Context) string {
if v, ok := ctx.Value(tokenKey{}).(string); ok {
return v
}
return ""
}