4abd4e68dc
Drop cli-proxy-api token handling, use only native Claude credentials. Simplify refresh to single endpoint (platform.claude.com) with scope. Add debug/refresh and debug/shutdown endpoints. Graceful shutdown.
103 lines
1.9 KiB
Go
103 lines
1.9 KiB
Go
package auth
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type Pool struct {
|
|
creds []*Credential
|
|
cursor int
|
|
mu sync.Mutex
|
|
}
|
|
|
|
func NewPool(creds []*Credential) *Pool {
|
|
return &Pool{creds: creds}
|
|
}
|
|
|
|
func (p *Pool) Pick() (*Credential, error) {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
n := len(p.creds)
|
|
if n == 0 {
|
|
return nil, fmt.Errorf("no credentials available")
|
|
}
|
|
|
|
for i := 0; i < n; i++ {
|
|
idx := (p.cursor + i) % n
|
|
cred := p.creds[idx]
|
|
if !cred.IsOnCooldown() {
|
|
p.cursor = (idx + 1) % n
|
|
return cred, nil
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("all %d credentials are on cooldown", n)
|
|
}
|
|
|
|
func (p *Pool) MarkFailure(cred *Credential, statusCode int) {
|
|
switch {
|
|
case statusCode == 429:
|
|
cred.SetCooldown(30 * time.Second)
|
|
case statusCode >= 500:
|
|
cred.SetCooldown(5 * time.Second)
|
|
}
|
|
}
|
|
|
|
func (p *Pool) MarkSuccess(cred *Credential) {
|
|
cred.mu.Lock()
|
|
defer cred.mu.Unlock()
|
|
cred.CooldownUntil = time.Time{}
|
|
}
|
|
|
|
func (p *Pool) RefreshExpiring(ctx context.Context) {
|
|
refreshExpiring(p)
|
|
}
|
|
|
|
func (p *Pool) RefreshAll(ctx context.Context) []map[string]string {
|
|
p.mu.Lock()
|
|
creds := make([]*Credential, len(p.creds))
|
|
copy(creds, p.creds)
|
|
p.mu.Unlock()
|
|
|
|
var results []map[string]string
|
|
for _, cred := range creds {
|
|
cred.mu.Lock()
|
|
id := cred.ID
|
|
email := cred.Email
|
|
oldExpiry := cred.ExpiresAt
|
|
hasRefresh := cred.RefreshToken != ""
|
|
cred.mu.Unlock()
|
|
|
|
r := map[string]string{
|
|
"id": id,
|
|
"email": email,
|
|
"old_expiry": oldExpiry.Format(time.RFC3339),
|
|
}
|
|
|
|
if !hasRefresh {
|
|
r["status"] = "skipped"
|
|
r["reason"] = "no refresh token"
|
|
results = append(results, r)
|
|
continue
|
|
}
|
|
|
|
err := RefreshToken(ctx, cred)
|
|
if err != nil {
|
|
r["status"] = "error"
|
|
r["error"] = err.Error()
|
|
} else {
|
|
cred.mu.Lock()
|
|
r["status"] = "ok"
|
|
r["new_expiry"] = cred.ExpiresAt.Format(time.RFC3339)
|
|
r["new_token_prefix"] = cred.AccessToken[:20] + "..."
|
|
cred.mu.Unlock()
|
|
}
|
|
results = append(results, r)
|
|
}
|
|
return results
|
|
}
|