mirror of
https://github.com/hanzoai/agents.git
synced 2026-10-30 18:48:36 +00:00
Per global rule: /v1/ only, never /api/. Internal API calls + handler comments + route registrations updated. - Control plane: agentAPI mount moves from /api/v1 -> /v1; uiAPI from /api/ui/v1 -> /v1/ui; uiAPIV2 from /api/ui/v2 -> /v2/ui. Auth middleware /api/v1/health -> /v1/health. All UI handler comments updated. - Go SDK (sdk/go/agent, sdk/go/client): control_plane_memory_backend, discovery, agent, note, client now use /v1/* and /v1/ui/* canonically. - LegacyExecutionEndpoints catalog in pkg/types/execution.go normalized to /v1/* and /v1/ui/* (UIWorkflowRunList stays /v2/ui as it's the v2 surface). - All test fixtures updated to match the new contract. Build note: pre-existing rot in internal/utils/paths.go and internal/handlers/ui/env.go (hanzo-agentsHome is not a valid Go identifier; hyphens not allowed) prevents `go build ./...` from succeeding — this predates the sweep and is not introduced here.
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, "/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()
|
|
}
|
|
}
|