File size: 6,191 Bytes
f606b10 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
package copilot
import (
"errors"
"fmt"
"net/http"
)
// OAuthError represents an OAuth-specific error.
type OAuthError struct {
// Code is the OAuth error code.
Code string `json:"error"`
// Description is a human-readable description of the error.
Description string `json:"error_description,omitempty"`
// URI is a URI identifying a human-readable web page with information about the error.
URI string `json:"error_uri,omitempty"`
// StatusCode is the HTTP status code associated with the error.
StatusCode int `json:"-"`
}
// Error returns a string representation of the OAuth error.
func (e *OAuthError) Error() string {
if e.Description != "" {
return fmt.Sprintf("OAuth error %s: %s", e.Code, e.Description)
}
return fmt.Sprintf("OAuth error: %s", e.Code)
}
// NewOAuthError creates a new OAuth error with the specified code, description, and status code.
func NewOAuthError(code, description string, statusCode int) *OAuthError {
return &OAuthError{
Code: code,
Description: description,
StatusCode: statusCode,
}
}
// AuthenticationError represents authentication-related errors.
type AuthenticationError struct {
// Type is the type of authentication error.
Type string `json:"type"`
// Message is a human-readable message describing the error.
Message string `json:"message"`
// Code is the HTTP status code associated with the error.
Code int `json:"code"`
// Cause is the underlying error that caused this authentication error.
Cause error `json:"-"`
}
// Error returns a string representation of the authentication error.
func (e *AuthenticationError) Error() string {
if e.Cause != nil {
return fmt.Sprintf("%s: %s (caused by: %v)", e.Type, e.Message, e.Cause)
}
return fmt.Sprintf("%s: %s", e.Type, e.Message)
}
// Unwrap returns the underlying cause of the error.
func (e *AuthenticationError) Unwrap() error {
return e.Cause
}
// Common authentication error types for GitHub Copilot device flow.
var (
// ErrDeviceCodeFailed represents an error when requesting the device code fails.
ErrDeviceCodeFailed = &AuthenticationError{
Type: "device_code_failed",
Message: "Failed to request device code from GitHub",
Code: http.StatusBadRequest,
}
// ErrDeviceCodeExpired represents an error when the device code has expired.
ErrDeviceCodeExpired = &AuthenticationError{
Type: "device_code_expired",
Message: "Device code has expired. Please try again.",
Code: http.StatusGone,
}
// ErrAuthorizationPending represents a pending authorization state (not an error, used for polling).
ErrAuthorizationPending = &AuthenticationError{
Type: "authorization_pending",
Message: "Authorization is pending. Waiting for user to authorize.",
Code: http.StatusAccepted,
}
// ErrSlowDown represents a request to slow down polling.
ErrSlowDown = &AuthenticationError{
Type: "slow_down",
Message: "Polling too frequently. Slowing down.",
Code: http.StatusTooManyRequests,
}
// ErrAccessDenied represents an error when the user denies authorization.
ErrAccessDenied = &AuthenticationError{
Type: "access_denied",
Message: "User denied authorization",
Code: http.StatusForbidden,
}
// ErrTokenExchangeFailed represents an error when token exchange fails.
ErrTokenExchangeFailed = &AuthenticationError{
Type: "token_exchange_failed",
Message: "Failed to exchange device code for access token",
Code: http.StatusBadRequest,
}
// ErrPollingTimeout represents an error when polling times out.
ErrPollingTimeout = &AuthenticationError{
Type: "polling_timeout",
Message: "Timeout waiting for user authorization",
Code: http.StatusRequestTimeout,
}
// ErrUserInfoFailed represents an error when fetching user info fails.
ErrUserInfoFailed = &AuthenticationError{
Type: "user_info_failed",
Message: "Failed to fetch GitHub user information",
Code: http.StatusBadRequest,
}
)
// NewAuthenticationError creates a new authentication error with a cause based on a base error.
func NewAuthenticationError(baseErr *AuthenticationError, cause error) *AuthenticationError {
return &AuthenticationError{
Type: baseErr.Type,
Message: baseErr.Message,
Code: baseErr.Code,
Cause: cause,
}
}
// IsAuthenticationError checks if an error is an authentication error.
func IsAuthenticationError(err error) bool {
var authenticationError *AuthenticationError
ok := errors.As(err, &authenticationError)
return ok
}
// IsOAuthError checks if an error is an OAuth error.
func IsOAuthError(err error) bool {
var oAuthError *OAuthError
ok := errors.As(err, &oAuthError)
return ok
}
// GetUserFriendlyMessage returns a user-friendly error message based on the error type.
func GetUserFriendlyMessage(err error) string {
var authErr *AuthenticationError
if errors.As(err, &authErr) {
switch authErr.Type {
case "device_code_failed":
return "Failed to start GitHub authentication. Please check your network connection and try again."
case "device_code_expired":
return "The authentication code has expired. Please try again."
case "authorization_pending":
return "Waiting for you to authorize the application on GitHub."
case "slow_down":
return "Please wait a moment before trying again."
case "access_denied":
return "Authentication was cancelled or denied."
case "token_exchange_failed":
return "Failed to complete authentication. Please try again."
case "polling_timeout":
return "Authentication timed out. Please try again."
case "user_info_failed":
return "Failed to get your GitHub account information. Please try again."
default:
return "Authentication failed. Please try again."
}
}
var oauthErr *OAuthError
if errors.As(err, &oauthErr) {
switch oauthErr.Code {
case "access_denied":
return "Authentication was cancelled or denied."
case "invalid_request":
return "Invalid authentication request. Please try again."
case "server_error":
return "GitHub server error. Please try again later."
default:
return fmt.Sprintf("Authentication failed: %s", oauthErr.Description)
}
}
return "An unexpected error occurred. Please try again."
}
|