Files
anthropic-proxy/internal/server/server.go
T
Alexander c4c1d4daa4 Anthropic API proxy with OAuth credential rotation and Claude Code fingerprinting
Sniffs a real Claude Code request on startup to capture exact HTTP headers,
then replays them for all proxied requests. Injects the billing header with
per-request SHA256 fingerprint into the system prompt. Uses utls with Chrome
TLS fingerprint to pass Cloudflare's bot detection on api.anthropic.com.

Supports both streaming (SSE) and non-streaming modes, round-robin credential
selection with automatic failover, and loading OAuth tokens from both
cli-proxy-api auth files and native ~/.claude/.credentials.json.
2026-04-09 21:05:32 +02:00

88 lines
2.0 KiB
Go

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()
}
}