Files
gitea-mcp/internal/auth/caller.go
T
mathiasandClaude Sonnet 5 5601927dc8
CD / Lint / Test / Vet (push) Successful in 10s
CD / Build & Import (push) Successful in 26s
CD / Deploy via GitOps (push) Has been skipped
feat(auth): per-caller Gitea PAT pass-through (#59)
Replaces the shared GITEA_MCP_DEFAULT_TOKEN for all callers. When a
request's bearer validates directly against Gitea's own /api/v1/user,
that token is used for every upstream call this request makes instead
of the service PAT, and the caller identity comes from Gitea's own
login rather than the proxy header. Any other bearer (static token,
JWT, none) falls through unchanged to the existing chassis auth.

Prep: Authentik now SSOs into Gitea (infra a32801c), so each real user
can mint their own PAT from their own linked account.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 08:22:50 +02:00

53 lines
1.7 KiB
Go

package auth
import (
"context"
"log/slog"
"net/http"
)
type ctxKey struct{}
// CallerMiddleware extracts the authenticated username from the reverse-proxy
// identity headers and stashes it in the request context for Caller().
//
// Header precedence: X-Auth-Request-User takes priority over X-Forwarded-User.
// X-Auth-Request-User is the header oauth2-proxy sets from the *verified* OIDC
// identity, so it is authoritative. X-Forwarded-User is a weaker, proxy-set
// convention some setups populate instead; it is used only as a fallback when
// X-Auth-Request-User is absent. If a proxy sets BOTH and they disagree, the
// verified X-Auth-Request-User still wins and we log a warning so the
// misconfiguration is visible rather than silently resolved (#10).
//
// logger may be nil, in which case the conflict warning is skipped.
func CallerMiddleware(logger *slog.Logger, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authUser := r.Header.Get("X-Auth-Request-User")
fwdUser := r.Header.Get("X-Forwarded-User")
user := authUser
if user == "" {
user = fwdUser
}
if logger != nil && authUser != "" && fwdUser != "" && authUser != fwdUser {
logger.Warn("conflicting caller identity headers; using X-Auth-Request-User",
"x_auth_request_user", authUser,
"x_forwarded_user", fwdUser)
}
next.ServeHTTP(w, r.WithContext(withCaller(r.Context(), user)))
})
}
func withCaller(ctx context.Context, user string) context.Context {
return context.WithValue(ctx, ctxKey{}, user)
}
func Caller(ctx context.Context) string {
if v, ok := ctx.Value(ctxKey{}).(string); ok {
return v
}
return ""
}