@ -32,6 +32,7 @@ const (
headerRateLimit = "X-RateLimit-Limit"
headerRateRemaining = "X-RateLimit-Remaining"
headerRateReset = "X-RateLimit-Reset"
headerOTP = "X-GitHub-OTP"
mediaTypeV3 = "application/vnd.github.v3+json"
defaultMediaType = "application/octet-stream"
@ -357,8 +358,15 @@ func (r *ErrorResponse) Error() string {
r . Response . StatusCode , r . Message , r . Errors )
}
// sanitizeURL redacts the client_id and client_secret tokens from the URL which
// may be exposed to the user, specifically in the ErrorResponse error message.
// TwoFactorAuthError occurs when using HTTP Basic Authentication for a user
// that has two-factor authentication enabled. The request can be reattempted
// by providing a one-time password in the request.
type TwoFactorAuthError ErrorResponse
func ( r * TwoFactorAuthError ) Error ( ) string { return ( * ErrorResponse ) ( r ) . Error ( ) }
// sanitizeURL redacts the client_secret parameter from the URL which may be
// exposed to the user, specifically in the ErrorResponse error message.
func sanitizeURL ( uri * url . URL ) * url . URL {
if uri == nil {
return nil
@ -411,6 +419,9 @@ func CheckResponse(r *http.Response) error {
if err == nil && data != nil {
json . Unmarshal ( data , errorResponse )
}
if r . StatusCode == http . StatusUnauthorized && strings . HasPrefix ( r . Header . Get ( headerOTP ) , "required" ) {
return ( * TwoFactorAuthError ) ( errorResponse )
}
return errorResponse
}
@ -562,6 +573,43 @@ func (t *UnauthenticatedRateLimitedTransport) transport() http.RoundTripper {
return http . DefaultTransport
}
// BasicAuthTransport is an http.RoundTripper that authenticates all requests
// using HTTP Basic Authentication with the provided username and password. It
// additionally supports users who have two-factor authentication enabled on
// their GitHub account.
type BasicAuthTransport struct {
Username string // GitHub username
Password string // GitHub password
OTP string // one-time password for users with two-factor auth enabled
// Transport is the underlying HTTP transport to use when making requests.
// It will default to http.DefaultTransport if nil.
Transport http . RoundTripper
}
// RoundTrip implements the RoundTripper interface.
func ( t * BasicAuthTransport ) RoundTrip ( req * http . Request ) ( * http . Response , error ) {
req = cloneRequest ( req ) // per RoundTrip contract
req . SetBasicAuth ( t . Username , t . Password )
if t . OTP != "" {
req . Header . Add ( headerOTP , t . OTP )
}
return t . transport ( ) . RoundTrip ( req )
}
// Client returns an *http.Client that makes requests that are authenticated
// using HTTP Basic Authentication.
func ( t * BasicAuthTransport ) Client ( ) * http . Client {
return & http . Client { Transport : t }
}
func ( t * BasicAuthTransport ) transport ( ) http . RoundTripper {
if t . Transport != nil {
return t . Transport
}
return http . DefaultTransport
}
// cloneRequest returns a clone of the provided *http.Request. The clone is a
// shallow copy of the struct and its Header map.
func cloneRequest ( r * http . Request ) * http . Request {
@ -569,9 +617,9 @@ func cloneRequest(r *http.Request) *http.Request {
r2 := new ( http . Request )
* r2 = * r
// deep copy of the Header
r2 . Header = make ( http . Header )
r2 . Header = make ( http . Header , len ( r . Header ) )
for k , s := range r . Header {
r2 . Header [ k ] = s
r2 . Header [ k ] = append ( [ ] string ( nil ) , s ... )
}
return r2
}