mirror of
https://github.com/router-for-me/CLIProxyAPIPlus.git
synced 2026-03-08 06:43:41 +00:00
- Add Unwrap() to AuthenticationError for proper error chain handling with errors.Is/As - Extract hardcoded header values to constants for maintainability - Replace verbose status code checks with isHTTPSuccess() helper - Remove unused ExtractBearerToken() and BuildModelsURL() functions - Make buildChatCompletionURL() private (only used internally) - Remove unused 'strings' import
188 lines
6.0 KiB
Go
188 lines
6.0 KiB
Go
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."
|
|
}
|