github.com/anthropics/anthropic-sdk-go@v1.70.1/internal/auth/error.go (about) 1 package auth 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "strings" 8 9 "github.com/anthropics/anthropic-sdk-go/config" 10 ) 11 12 // ErrNoCredentials is the sentinel for the case where the default credential 13 // chain produced no usable credential. Every [NoCredentialsError] value 14 // matches this sentinel via [errors.Is], so callers can branch on the 15 // class of failure without a type assertion. 16 var ErrNoCredentials = errors.New("no Anthropic credentials found") 17 18 // OAuthTokenError is returned when an OAuth token request (workload identity 19 // exchange or authorized_user refresh) fails with a non-200 status. 20 type OAuthTokenError struct { 21 StatusCode int 22 Body string 23 // RequestID is the value of the server's Request-Id response header, if 24 // any. Empty when the response had no such header. Included in the 25 // formatted error so support can correlate user reports with server logs. 26 RequestID string 27 // Hint is an optional caller-supplied diagnostic appended verbatim to 28 // the error message. Callers gate it on the response status and their 29 // own state — Error does not inspect StatusCode or Body to decide 30 // whether to include it. 31 Hint string 32 // WorkloadIdentity is set by the OIDC federation (jwt-bearer) flow to 33 // suppress the "re-run `ant auth login`" remediation suffix. 34 // That suffix only makes sense for the interactive user_oauth flow; 35 // machine credentials have no browser login to re-run, and suggesting 36 // one sends operators down the wrong path. The zero value keeps the 37 // suffix so the user_oauth flow needs no change at its call site. 38 WorkloadIdentity bool 39 } 40 41 // rfc6749ErrorBody is the subset of the RFC 6749 OAuth error response the SDK 42 // surfaces in user-facing error messages. Extra fields are ignored. 43 type rfc6749ErrorBody struct { 44 Error string `json:"error"` 45 ErrorDescription string `json:"error_description"` 46 } 47 48 func (e *OAuthTokenError) Error() string { 49 parts := []string{fmt.Sprintf("oauth token request failed (status %d)", e.StatusCode)} 50 if e.RequestID != "" { 51 parts = append(parts, fmt.Sprintf("request id %s", e.RequestID)) 52 } 53 54 var parsed rfc6749ErrorBody 55 if err := json.Unmarshal([]byte(e.Body), &parsed); err == nil && parsed.Error != "" { 56 if parsed.ErrorDescription != "" { 57 parts = append(parts, fmt.Sprintf("%s: %s", parsed.Error, parsed.ErrorDescription)) 58 } else { 59 parts = append(parts, parsed.Error) 60 } 61 } else { 62 parts = append(parts, config.RedactOAuthErrorBody(e.Body)) 63 } 64 65 msg := strings.Join(parts, "; ") 66 if !e.WorkloadIdentity && shouldSuggestRelogin(e.StatusCode, parsed.Error) { 67 msg += " — re-run `ant auth login` to re-authenticate" 68 } 69 if e.Hint != "" { 70 msg += ". " + e.Hint 71 } 72 return msg 73 } 74 75 // shouldSuggestRelogin returns true when a token-endpoint failure is most 76 // likely caused by an expired or revoked refresh credential. 5xx is treated 77 // as transient; blaming the user's credentials on a server outage is wrong. 78 func shouldSuggestRelogin(status int, rfcCode string) bool { 79 if rfcCode == "invalid_grant" { 80 return true 81 } 82 return status == 400 || status == 401 || status == 403 83 } 84 85 // CredentialSourceState categorizes the outcome of one entry in the default 86 // credential chain when no source produced a usable credential. 87 type CredentialSourceState int 88 89 const ( 90 CredentialSourceNotSet CredentialSourceState = iota 91 CredentialSourceNotFound 92 CredentialSourceLoadFailed 93 CredentialSourcePartial 94 ) 95 96 // CredentialSourceStatus records what the default resolution chain observed 97 // for one credential source when all sources failed. Detail is the load 98 // error for [CredentialSourceLoadFailed], the remediation hint for 99 // [CredentialSourceNotFound], or the missing-var list for 100 // [CredentialSourcePartial]; it is empty for [CredentialSourceNotSet]. 101 type CredentialSourceStatus struct { 102 Name string 103 State CredentialSourceState 104 Detail string 105 } 106 107 // NoCredentialsError is returned by the default credential chain when no 108 // source produced a usable credential. Its Error method renders every 109 // source the SDK tried and a brief remediation hint. 110 type NoCredentialsError struct { 111 Sources []CredentialSourceStatus 112 } 113 114 // Is reports whether target is [ErrNoCredentials]. Enables callers to 115 // detect the no-credentials class with [errors.Is] in addition to 116 // [errors.As] against the concrete type. 117 func (e *NoCredentialsError) Is(target error) bool { 118 return target == ErrNoCredentials 119 } 120 121 func (e *NoCredentialsError) Error() string { 122 var b strings.Builder 123 b.WriteString("no Anthropic credentials found. The SDK tried these sources in order:") 124 for i, s := range e.Sources { 125 fmt.Fprintf(&b, "\n %d. %s: %s", i+1, s.Name, formatSourceState(s)) 126 } 127 b.WriteString("\nTo fix:") 128 b.WriteString("\n - run `ant auth login` to interactively authenticate, or") 129 b.WriteString("\n - set ANTHROPIC_API_KEY, or") 130 b.WriteString("\n - configure workload identity federation with ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID, and ANTHROPIC_IDENTITY_TOKEN_FILE") 131 return b.String() 132 } 133 134 func formatSourceState(s CredentialSourceStatus) string { 135 switch s.State { 136 case CredentialSourceNotFound: 137 if s.Detail != "" { 138 return "not found (" + s.Detail + ")" 139 } 140 return "not found" 141 case CredentialSourceLoadFailed: 142 return "load failed: " + s.Detail 143 case CredentialSourcePartial: 144 return s.Detail 145 default: 146 return "not set" 147 } 148 } 149 150 // CredentialResolutionError is returned when a credential provider cannot be 151 // constructed from the available configuration. 152 type CredentialResolutionError struct { 153 Message string 154 Err error 155 } 156 157 func (e *CredentialResolutionError) Error() string { 158 if e.Err != nil { 159 return fmt.Sprintf("credential resolution error: %s: %v", e.Message, e.Err) 160 } 161 return fmt.Sprintf("credential resolution error: %s", e.Message) 162 } 163 164 func (e *CredentialResolutionError) Unwrap() error { 165 return e.Err 166 }