Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions proxy/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -1425,6 +1425,24 @@ func parseRetryAfter(body []byte) time.Duration {
return 2 * time.Minute
}

func isMissingScopeUnauthorized(body []byte) bool {
if len(body) == 0 {
return false
}

code := strings.ToLower(strings.TrimSpace(gjson.GetBytes(body, "error.code").String()))
if code != "missing_scope" {
return false
}

msg := strings.ToLower(gjson.GetBytes(body, "error.message").String())
if strings.Contains(msg, "api.responses.write") {
return true
}

return strings.Contains(msg, "scope")
}

// applyCooldown 根据上游状态码设置智能冷却
func (h *Handler) applyCooldown(account *auth.Account, statusCode int, body []byte, resp *http.Response) {
switch statusCode {
Expand All @@ -1436,6 +1454,12 @@ func (h *Handler) applyCooldown(account *auth.Account, statusCode int, body []by
// 原子标志瞬间置位,阻止其他并发请求再选到该账号
atomic.StoreInt32(&account.Disabled, 1)

if isMissingScopeUnauthorized(body) {
log.Printf("账号 %d 收到 missing_scope 401,保留在号池", account.ID())
atomic.StoreInt32(&account.Disabled, 0)
return
}

if h.store.GetAutoCleanUnauthorized() {
// 开启自动清理时,401 立即从号池删除
log.Printf("账号 %d 收到 401,立即清理", account.ID())
Expand Down
46 changes: 46 additions & 0 deletions proxy/handler_scope_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package proxy

import "testing"

func TestIsMissingScopeUnauthorized(t *testing.T) {
tests := []struct {
name string
body string
want bool
}{
{
name: "missing scope for responses write",
body: `{"error":{"message":"Missing required scope: api.responses.write","type":"invalid_request_error","code":"missing_scope"}}`,
want: true,
},
{
name: "missing scope generic message",
body: `{"error":{"message":"missing scope for this operation","type":"invalid_request_error","code":"missing_scope"}}`,
want: true,
},
{
name: "unauthorized invalid api key",
body: `{"error":{"message":"Invalid API key","type":"invalid_request_error","code":"invalid_api_key"}}`,
want: false,
},
{
name: "empty body",
body: ``,
want: false,
},
{
name: "invalid json",
body: `not-json`,
want: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := isMissingScopeUnauthorized([]byte(tt.body))
if got != tt.want {
t.Fatalf("isMissingScopeUnauthorized() = %v, want %v", got, tt.want)
}
})
}
}