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."
}