- internal/auth/jwt.go: JWTValidator via lestrrat-go/jwx/v2, JWKS auto-refresh - internal/auth/bearer.go: replace Gitea PAT validation with JWT->static->default chain - internal/gitea/client.go: always use service PAT; remove TokenFromContext lookup - internal/config/config.go: add DexIssuerURL, MCPAudience, MCPResourceURL, StaticToken - cmd/gitea-mcp/main.go: wire validator, fix /.well-known to return real AS list - bearer_test.go: rewrite for new API
44 lines
1.2 KiB
Go
44 lines
1.2 KiB
Go
package auth
|
|
|
|
import (
|
|
"crypto/subtle"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
// BearerMiddleware authenticates requests via one of three paths (in order):
|
|
//
|
|
// 1. Bearer token is a valid JWT issued by the configured Dex OIDC server.
|
|
// 2. Bearer token matches staticToken (constant-time compare).
|
|
// 3. No Authorization header and defaultToken is set — allow through; the
|
|
// Gitea client will use its service PAT for upstream calls.
|
|
//
|
|
// Any other case returns 401.
|
|
func BearerMiddleware(jwtValidator *JWTValidator, staticToken, defaultToken string, next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
bearer, hasBearer := strings.CutPrefix(r.Header.Get("Authorization"), "Bearer ")
|
|
hasBearer = hasBearer && bearer != ""
|
|
|
|
if !hasBearer {
|
|
if defaultToken != "" {
|
|
next.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
if jwtValidator.Validate(r.Context(), bearer) {
|
|
next.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
if staticToken != "" && subtle.ConstantTimeCompare([]byte(bearer), []byte(staticToken)) == 1 {
|
|
next.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
|
})
|
|
}
|