Remove dead code, secure debug endpoints, fix encapsulation
This commit is contained in:
@@ -18,7 +18,6 @@ cp config.example.yaml config.yaml
|
|||||||
Edit `config.yaml`:
|
Edit `config.yaml`:
|
||||||
- `api_keys` — key(s) your clients use to authenticate with the proxy
|
- `api_keys` — key(s) your clients use to authenticate with the proxy
|
||||||
- `claude_credentials` — path to your Claude credentials file
|
- `claude_credentials` — path to your Claude credentials file
|
||||||
- `auth_dir` — optional, directory with additional OAuth credential JSON files
|
|
||||||
- `claude_binary` — path to `claude` binary (used on startup to capture request fingerprint)
|
- `claude_binary` — path to `claude` binary (used on startup to capture request fingerprint)
|
||||||
|
|
||||||
## Build and run
|
## Build and run
|
||||||
|
|||||||
@@ -48,9 +48,7 @@ func (p *Pool) MarkFailure(cred *Credential, statusCode int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pool) MarkSuccess(cred *Credential) {
|
func (p *Pool) MarkSuccess(cred *Credential) {
|
||||||
cred.mu.Lock()
|
cred.ClearCooldown()
|
||||||
defer cred.mu.Unlock()
|
|
||||||
cred.CooldownUntil = time.Time{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pool) RefreshExpiring(ctx context.Context) {
|
func (p *Pool) RefreshExpiring(ctx context.Context) {
|
||||||
|
|||||||
@@ -18,13 +18,6 @@ type Credential struct {
|
|||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsExpired returns true if the credential's access token has expired.
|
|
||||||
func (c *Credential) IsExpired() bool {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
return time.Now().After(c.ExpiresAt)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsOnCooldown returns true if the credential is currently on cooldown.
|
// IsOnCooldown returns true if the credential is currently on cooldown.
|
||||||
func (c *Credential) IsOnCooldown() bool {
|
func (c *Credential) IsOnCooldown() bool {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
@@ -39,6 +32,13 @@ func (c *Credential) SetCooldown(duration time.Duration) {
|
|||||||
c.CooldownUntil = time.Now().Add(duration)
|
c.CooldownUntil = time.Now().Add(duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClearCooldown removes any active cooldown on the credential.
|
||||||
|
func (c *Credential) ClearCooldown() {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
c.CooldownUntil = time.Time{}
|
||||||
|
}
|
||||||
|
|
||||||
// Token returns the current access token.
|
// Token returns the current access token.
|
||||||
func (c *Credential) Token() string {
|
func (c *Credential) Token() string {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
|
|||||||
+1
-21
@@ -10,8 +10,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tidwall/gjson"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// SniffedProfile holds everything captured from a real Claude Code request.
|
// SniffedProfile holds everything captured from a real Claude Code request.
|
||||||
@@ -124,9 +122,8 @@ func SniffClaudeCode(claudeBinary string) (*SniffedProfile, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func extractProfile(r *http.Request, body []byte) *SniffedProfile {
|
func extractProfile(r *http.Request, body []byte) *SniffedProfile {
|
||||||
// Capture raw headers preserving original casing and order.
|
// Capture raw headers preserving original casing.
|
||||||
var headers [][2]string
|
var headers [][2]string
|
||||||
for i := 0; i < len(r.Header); i++ {
|
|
||||||
for name, vals := range r.Header {
|
for name, vals := range r.Header {
|
||||||
if skipHeaders[strings.ToLower(name)] {
|
if skipHeaders[strings.ToLower(name)] {
|
||||||
continue
|
continue
|
||||||
@@ -135,8 +132,6 @@ func extractProfile(r *http.Request, body []byte) *SniffedProfile {
|
|||||||
headers = append(headers, [2]string{name, v})
|
headers = append(headers, [2]string{name, v})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deduplicate and strip subscription-specific betas.
|
// Deduplicate and strip subscription-specific betas.
|
||||||
seen := map[string]bool{}
|
seen := map[string]bool{}
|
||||||
@@ -170,21 +165,6 @@ func extractProfile(r *http.Request, body []byte) *SniffedProfile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract the system prompt template from the body (everything except the billing header block).
|
|
||||||
// The billing header is the first system block starting with "x-anthropic-billing-header:".
|
|
||||||
systemBlocks := gjson.GetBytes(body, "system")
|
|
||||||
var templateSystem []string
|
|
||||||
if systemBlocks.IsArray() {
|
|
||||||
for _, block := range systemBlocks.Array() {
|
|
||||||
text := block.Get("text").String()
|
|
||||||
if strings.HasPrefix(text, "x-anthropic-billing-header:") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
templateSystem = append(templateSystem, block.Raw)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ = templateSystem // stored in body for now
|
|
||||||
|
|
||||||
return &SniffedProfile{
|
return &SniffedProfile{
|
||||||
Headers: deduped,
|
Headers: deduped,
|
||||||
Body: body,
|
Body: body,
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
@@ -47,7 +46,6 @@ func New(cfg *config.Config, pool *auth.Pool, profile *proxy.SniffedProfile) *Se
|
|||||||
|
|
||||||
engine.POST("/reload", s.handleReload())
|
engine.POST("/reload", s.handleReload())
|
||||||
engine.POST("/debug/refresh", handleDebugRefresh(pool))
|
engine.POST("/debug/refresh", handleDebugRefresh(pool))
|
||||||
engine.POST("/debug/shutdown", handleDebugShutdown(s))
|
|
||||||
engine.GET("/healthz", func(c *gin.Context) {
|
engine.GET("/healthz", func(c *gin.Context) {
|
||||||
c.JSON(http.StatusOK, gin.H{"status": "ok"})
|
c.JSON(http.StatusOK, gin.H{"status": "ok"})
|
||||||
})
|
})
|
||||||
@@ -100,19 +98,6 @@ func (s *Server) handleReload() gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleDebugShutdown(s *Server) gin.HandlerFunc {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
c.JSON(http.StatusOK, gin.H{"status": "shutting down"})
|
|
||||||
go func() {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
if err := s.Shutdown(ctx); err != nil {
|
|
||||||
log.Printf("shutdown error: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleDebugRefresh(pool *auth.Pool) gin.HandlerFunc {
|
func handleDebugRefresh(pool *auth.Pool) gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
results := pool.RefreshAll(c.Request.Context())
|
results := pool.RefreshAll(c.Request.Context())
|
||||||
@@ -146,7 +131,7 @@ func corsMiddleware() gin.HandlerFunc {
|
|||||||
func (s *Server) authMiddleware() gin.HandlerFunc {
|
func (s *Server) authMiddleware() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
path := c.Request.URL.Path
|
path := c.Request.URL.Path
|
||||||
if path == "/healthz" || path == "/reload" || strings.HasPrefix(path, "/debug/") {
|
if path == "/healthz" || path == "/reload" {
|
||||||
c.Next()
|
c.Next()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user