mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-07-28 01:27:58 +00:00
143 lines
3.6 KiB
Go
143 lines
3.6 KiB
Go
package providers
|
|
|
|
import (
|
|
"errors"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/sipeed/picoclaw/pkg/providers/common"
|
|
)
|
|
|
|
type AuthErrorKind string
|
|
|
|
const (
|
|
AuthErrorInvalidAPIKey AuthErrorKind = "invalid_api_key"
|
|
AuthErrorMissingAPIKey AuthErrorKind = "missing_api_key"
|
|
AuthErrorExpiredToken AuthErrorKind = "expired_token"
|
|
AuthErrorGeneric AuthErrorKind = "auth"
|
|
)
|
|
|
|
var (
|
|
invalidAPIKeyPattern = regexp.MustCompile(
|
|
`(?i)\b(?:invalid|incorrect|malformed|wrong)[-_\s]+(?:api[-_\s]*)?key\b|\b(?:api[-_\s]*)?key[-_\s]+(?:is[-_\s]+)?(?:invalid|incorrect|malformed|wrong)\b|\binvalid[-_]?api[-_]?key\b`,
|
|
)
|
|
missingAPIKeyPattern = regexp.MustCompile(
|
|
`(?i)\b(?:missing|no|not[-_\s]+configured)[-_\s]+(?:api[-_\s]*)?key\b|\bno[-_\s]+credentials[-_\s]+found\b|\bapi[-_\s]+key[-_\s]+not[-_\s]+configured\b`,
|
|
)
|
|
expiredTokenPattern = regexp.MustCompile(
|
|
`(?i)\b(?:oauth[-_\s]+token[-_\s]+refresh[-_\s]+failed|re-authenticate|token[-_\s]+(?:has[-_\s]+)?expired|expired[-_\s]+token)\b`,
|
|
)
|
|
genericAuthPattern = regexp.MustCompile(
|
|
`(?i)\b(?:unauthorized|forbidden|authentication[-_\s]+(?:failed|required)|access[-_\s]+denied)\b`,
|
|
)
|
|
)
|
|
|
|
func ClassifyAuthError(err error) (AuthErrorKind, bool) {
|
|
if err == nil {
|
|
return "", false
|
|
}
|
|
|
|
var exhausted *FallbackExhaustedError
|
|
if errors.As(err, &exhausted) && exhausted != nil {
|
|
return classifyFallbackExhaustedAuthError(exhausted)
|
|
}
|
|
|
|
msg := authErrorText(err)
|
|
if missingAPIKeyPattern.MatchString(msg) {
|
|
return AuthErrorMissingAPIKey, true
|
|
}
|
|
if expiredTokenPattern.MatchString(msg) {
|
|
return AuthErrorExpiredToken, true
|
|
}
|
|
if invalidAPIKeyPattern.MatchString(msg) {
|
|
return AuthErrorInvalidAPIKey, true
|
|
}
|
|
|
|
if hasStructuredAuthError(err) || genericAuthPattern.MatchString(msg) {
|
|
return AuthErrorGeneric, true
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
func authErrorText(err error) string {
|
|
var parts []string
|
|
if err != nil {
|
|
parts = append(parts, err.Error())
|
|
}
|
|
|
|
var httpErr *common.HTTPError
|
|
if errors.As(err, &httpErr) && httpErr != nil {
|
|
parts = append(parts, httpErr.BodyPreview)
|
|
}
|
|
|
|
return strings.Join(parts, "\n")
|
|
}
|
|
|
|
func hasStructuredAuthError(err error) bool {
|
|
var failErr *FailoverError
|
|
if errors.As(err, &failErr) && failErr != nil && failErr.Reason == FailoverAuth {
|
|
return true
|
|
}
|
|
|
|
var httpErr *common.HTTPError
|
|
if errors.As(err, &httpErr) && httpErr != nil {
|
|
return httpErr.StatusCode == 401 || httpErr.StatusCode == 403
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func classifyFallbackExhaustedAuthError(err *FallbackExhaustedError) (AuthErrorKind, bool) {
|
|
if err == nil {
|
|
return "", false
|
|
}
|
|
|
|
var authMessages []string
|
|
nonSkipped := 0
|
|
for _, attempt := range err.Attempts {
|
|
if attempt.Skipped {
|
|
continue
|
|
}
|
|
nonSkipped++
|
|
if !attemptIsAuthFailure(attempt) {
|
|
return "", false
|
|
}
|
|
if attempt.Error != nil {
|
|
authMessages = append(authMessages, authErrorText(attempt.Error))
|
|
}
|
|
}
|
|
if nonSkipped == 0 {
|
|
return "", false
|
|
}
|
|
|
|
msg := strings.Join(authMessages, "\n")
|
|
if missingAPIKeyPattern.MatchString(msg) {
|
|
return AuthErrorMissingAPIKey, true
|
|
}
|
|
if expiredTokenPattern.MatchString(msg) {
|
|
return AuthErrorExpiredToken, true
|
|
}
|
|
if invalidAPIKeyPattern.MatchString(msg) {
|
|
return AuthErrorInvalidAPIKey, true
|
|
}
|
|
return AuthErrorGeneric, true
|
|
}
|
|
|
|
func attemptIsAuthFailure(attempt FallbackAttempt) bool {
|
|
if attempt.Reason == FailoverAuth {
|
|
return true
|
|
}
|
|
if attempt.Error == nil {
|
|
return false
|
|
}
|
|
var failErr *FailoverError
|
|
if errors.As(attempt.Error, &failErr) && failErr != nil && failErr.Reason == FailoverAuth {
|
|
return true
|
|
}
|
|
var httpErr *common.HTTPError
|
|
if errors.As(attempt.Error, &httpErr) && httpErr != nil {
|
|
return httpErr.StatusCode == 401 || httpErr.StatusCode == 403
|
|
}
|
|
return false
|
|
}
|