mirror of
https://github.com/hanzoai/agent.git
synced 2026-09-11 21:08:51 +00:00
Consolidate all Hanzo agent infrastructure into one repo: - control-plane/: Go control plane server (was agents-new) - sdk/: Go, Python, TypeScript SDKs - deployments/: Docker, Kubernetes, Helm configs - Dockerfile.sdk: Python SDK image (ghcr.io/hanzoai/agent:latest) - CI builds both SDK + control-plane images No more agentfield — everything is hanzoai/agent.
78 lines
1.7 KiB
Go
78 lines
1.7 KiB
Go
package middleware
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// AuthConfig mirrors server configuration for HTTP authentication.
|
|
type AuthConfig struct {
|
|
APIKey string
|
|
SkipPaths []string
|
|
}
|
|
|
|
// APIKeyAuth enforces API key authentication via header, bearer token, or query param.
|
|
func APIKeyAuth(config AuthConfig) gin.HandlerFunc {
|
|
skipPathSet := make(map[string]struct{}, len(config.SkipPaths))
|
|
for _, p := range config.SkipPaths {
|
|
skipPathSet[p] = struct{}{}
|
|
}
|
|
|
|
return func(c *gin.Context) {
|
|
// No auth configured, allow everything.
|
|
if config.APIKey == "" {
|
|
c.Next()
|
|
return
|
|
}
|
|
|
|
// Skip explicit paths
|
|
if _, ok := skipPathSet[c.Request.URL.Path]; ok {
|
|
c.Next()
|
|
return
|
|
}
|
|
|
|
// Always allow health and metrics by default
|
|
if strings.HasPrefix(c.Request.URL.Path, "/api/v1/health") || c.Request.URL.Path == "/health" || c.Request.URL.Path == "/metrics" {
|
|
c.Next()
|
|
return
|
|
}
|
|
|
|
// Allow UI static files to load (the React app handles auth prompting)
|
|
// Also allow root "/" which redirects to /ui/
|
|
if strings.HasPrefix(c.Request.URL.Path, "/ui") || c.Request.URL.Path == "/" {
|
|
c.Next()
|
|
return
|
|
}
|
|
|
|
apiKey := ""
|
|
|
|
// Preferred: X-API-Key header
|
|
apiKey = c.GetHeader("X-API-Key")
|
|
|
|
// Fallback: Authorization: Bearer <token>
|
|
if apiKey == "" {
|
|
authHeader := c.GetHeader("Authorization")
|
|
if strings.HasPrefix(authHeader, "Bearer ") {
|
|
apiKey = strings.TrimPrefix(authHeader, "Bearer ")
|
|
}
|
|
}
|
|
|
|
// SSE/WebSocket friendly: api_key query parameter
|
|
if apiKey == "" {
|
|
apiKey = c.Query("api_key")
|
|
}
|
|
|
|
if apiKey != config.APIKey {
|
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
|
"error": "unauthorized",
|
|
"message": "invalid or missing API key",
|
|
})
|
|
return
|
|
}
|
|
|
|
c.Next()
|
|
}
|
|
}
|