package server import ( "fmt" "net/http" "strings" "github.com/gin-gonic/gin" "github.com/fujin/anthropic-proxy/internal/auth" "github.com/fujin/anthropic-proxy/internal/config" "github.com/fujin/anthropic-proxy/internal/proxy" ) type Server struct { engine *gin.Engine port int } func New(cfg *config.Config, pool *auth.Pool, profile *proxy.SniffedProfile) *Server { gin.SetMode(gin.ReleaseMode) engine := gin.New() engine.Use(gin.Recovery()) engine.Use(corsMiddleware()) engine.Use(authMiddleware(cfg.APIKeys)) engine.POST("/v1/messages", proxy.HandleMessages(pool, profile)) engine.GET("/healthz", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"status": "ok"}) }) return &Server{engine: engine, port: cfg.Port} } func (s *Server) Start() error { addr := fmt.Sprintf(":%d", s.port) return s.engine.Run(addr) } func corsMiddleware() gin.HandlerFunc { return func(c *gin.Context) { c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") c.Header("Access-Control-Allow-Headers", "Origin, Content-Type, Authorization, x-api-key") if c.Request.Method == http.MethodOptions { c.AbortWithStatus(http.StatusNoContent) return } c.Next() } } func authMiddleware(apiKeys []string) gin.HandlerFunc { keySet := make(map[string]struct{}, len(apiKeys)) for _, k := range apiKeys { keySet[k] = struct{}{} } return func(c *gin.Context) { if c.Request.URL.Path == "/healthz" { c.Next() return } token := "" if authHeader := c.GetHeader("Authorization"); authHeader != "" { token = strings.TrimPrefix(authHeader, "Bearer ") } if token == "" { token = c.GetHeader("x-api-key") } if token == "" { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "missing authentication"}) return } if _, ok := keySet[token]; !ok { c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "invalid api key"}) return } c.Next() } }