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 "" }