package auth import ( "context" "fmt" "io" "net/http" "golang.org/x/oauth2" ) // baseProvider defines common fields and methods used by OAuth2 client providers. type baseProvider struct { ctx context.Context scopes []string clientId string clientSecret string redirectUrl string authUrl string tokenUrl string userApiUrl string } // Context implements Provider.Context() interface method. func (p *baseProvider) Context() context.Context { return p.ctx } // SetContext implements Provider.SetContext() interface method. func (p *baseProvider) SetContext(ctx context.Context) { p.ctx = ctx } // Scopes implements Provider.Scopes() interface method. func (p *baseProvider) Scopes() []string { return p.scopes } // SetScopes implements Provider.SetScopes() interface method. func (p *baseProvider) SetScopes(scopes []string) { p.scopes = scopes } // ClientId implements Provider.ClientId() interface method. func (p *baseProvider) ClientId() string { return p.clientId } // SetClientId implements Provider.SetClientId() interface method. func (p *baseProvider) SetClientId(clientId string) { p.clientId = clientId } // ClientSecret implements Provider.ClientSecret() interface method. func (p *baseProvider) ClientSecret() string { return p.clientSecret } // SetClientSecret implements Provider.SetClientSecret() interface method. func (p *baseProvider) SetClientSecret(secret string) { p.clientSecret = secret } // RedirectUrl implements Provider.RedirectUrl() interface method. func (p *baseProvider) RedirectUrl() string { return p.redirectUrl } // SetRedirectUrl implements Provider.SetRedirectUrl() interface method. func (p *baseProvider) SetRedirectUrl(url string) { p.redirectUrl = url } // AuthUrl implements Provider.AuthUrl() interface method. func (p *baseProvider) AuthUrl() string { return p.authUrl } // SetAuthUrl implements Provider.SetAuthUrl() interface method. func (p *baseProvider) SetAuthUrl(url string) { p.authUrl = url } // TokenUrl implements Provider.TokenUrl() interface method. func (p *baseProvider) TokenUrl() string { return p.tokenUrl } // SetTokenUrl implements Provider.SetTokenUrl() interface method. func (p *baseProvider) SetTokenUrl(url string) { p.tokenUrl = url } // UserApiUrl implements Provider.UserApiUrl() interface method. func (p *baseProvider) UserApiUrl() string { return p.userApiUrl } // SetUserApiUrl implements Provider.SetUserApiUrl() interface method. func (p *baseProvider) SetUserApiUrl(url string) { p.userApiUrl = url } // BuildAuthUrl implements Provider.BuildAuthUrl() interface method. func (p *baseProvider) BuildAuthUrl(state string, opts ...oauth2.AuthCodeOption) string { return p.oauth2Config().AuthCodeURL(state, opts...) } // FetchToken implements Provider.FetchToken() interface method. func (p *baseProvider) FetchToken(code string, opts ...oauth2.AuthCodeOption) (*oauth2.Token, error) { return p.oauth2Config().Exchange(p.ctx, code, opts...) } // Client implements Provider.Client() interface method. func (p *baseProvider) Client(token *oauth2.Token) *http.Client { return p.oauth2Config().Client(p.ctx, token) } // FetchRawUserData implements Provider.FetchRawUserData() interface method. func (p *baseProvider) FetchRawUserData(token *oauth2.Token) ([]byte, error) { req, err := http.NewRequestWithContext(p.ctx, "GET", p.userApiUrl, nil) if err != nil { return nil, err } return p.sendRawUserDataRequest(req, token) } // sendRawUserDataRequest sends the specified user data request and return its raw response body. func (p *baseProvider) sendRawUserDataRequest(req *http.Request, token *oauth2.Token) ([]byte, error) { client := p.Client(token) res, err := client.Do(req) if err != nil { return nil, err } defer res.Body.Close() result, err := io.ReadAll(res.Body) if err != nil { return nil, err } // http.Client.Get doesn't treat non 2xx responses as error if res.StatusCode >= 400 { return nil, fmt.Errorf( "failed to fetch OAuth2 user profile via %s (%d):\n%s", p.userApiUrl, res.StatusCode, string(result), ) } return result, nil } // oauth2Config constructs a oauth2.Config instance based on the provider settings. func (p *baseProvider) oauth2Config() *oauth2.Config { return &oauth2.Config{ RedirectURL: p.redirectUrl, ClientID: p.clientId, ClientSecret: p.clientSecret, Scopes: p.scopes, Endpoint: oauth2.Endpoint{ AuthURL: p.authUrl, TokenURL: p.tokenUrl, }, } }