Skip to content

Instantly share code, notes, and snippets.

@IamNator
Last active May 27, 2022 09:49
Show Gist options
  • Save IamNator/53aa880c4dc57c7f1141505e5f831aed to your computer and use it in GitHub Desktop.
Save IamNator/53aa880c4dc57c7f1141505e5f831aed to your computer and use it in GitHub Desktop.
package oauth2
import (
"context"
"encoding/json"
"errors"
"fmt"
"inawo-services-api/pkg/env"
"os"
"time"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/go-resty/resty/v2"
"github.com/rs/zerolog"
"golang.org/x/oauth2"
)
type Oauth2 struct {
provider *oidc.Provider
logger *zerolog.Logger
config *oauth2.Config
state func() string
client *resty.Client
AuthCodeURL string
}
type Options func(*oauth2.Config)
type UserInfo struct {
OAuth2Token *oauth2.Token
UserInfo *oidc.UserInfo
}
type UserProfile struct {
Sub string `json:"sub"`
Name string `json:"name"`
GivenName string `json:"given_name"`
FamilyName string `json:"family_name"`
Picture string `json:"picture"`
Locale string `json:"locale"`
}
func WithOCID() Options {
return func(o *oauth2.Config) {
o.Scopes = append(o.Scopes, oidc.ScopeOpenID)
}
}
func WithEmail() Options {
return func(o *oauth2.Config) {
o.Scopes = append(o.Scopes, "email")
}
}
func WithProfile() Options {
return func(o *oauth2.Config) {
o.Scopes = append(o.Scopes, "profile")
}
}
func WithAll() Options {
return func(o *oauth2.Config) {
o.Scopes = []string{oidc.ScopeOpenID, "email", "profile"}
}
}
func newRestyClient() *resty.Client {
// Create a Resty Client
client := resty.New()
client.SetDebug(env.Get().IsDEBUG)
// Retries are configured per client
client.
// Set retry count to non zero to enable retries
SetRetryCount(3).
// You can override initial retry wait time.
// Default is 100 milliseconds.
SetRetryWaitTime(10 * time.Millisecond).
// MaxWaitTime can be overridden as well.
// Default is 2 seconds.
SetRetryMaxWaitTime(150 * time.Millisecond).
// SetRetryAfter sets callback to calculate wait time between retries.
// Default (nil) implies exponential backoff with jitter
SetRetryAfter(func(client *resty.Client, resp *resty.Response) (time.Duration, error) {
return 0, errors.New("quota exceeded")
})
return client
}
func NewClient(opts ...Options) *Oauth2 {
logger := zerolog.New(os.Stdout).With().Str("api", "inawo_api_service").Str("module", "oauth2").Logger()
ctx := context.Background()
clientID := env.Get().OAUTH2.GOOGLE.ClIENT_ID
clientSecret := env.Get().OAUTH2.GOOGLE.CLIENT_SECRET
redirecturl := env.Get().OAUTH2.GOOGLE.REDIRECT_URL
provider, err := oidc.NewProvider(ctx, "https://accounts.google.com")
if err != nil {
logger.Error().Err(err).Msg("Error creating provider")
}
config := oauth2.Config{
ClientID: clientID,
ClientSecret: clientSecret,
Endpoint: provider.Endpoint(),
RedirectURL: redirecturl,
}
config.Scopes = []string{} //clear scope
//apply by default
if opts != nil {
opts = append(opts, WithOCID())
}
for _, option := range opts {
option(&config)
}
state := func() string {
return "some state"
}
return &Oauth2{
logger: &logger,
provider: provider,
config: &config,
state: state,
AuthCodeURL: config.AuthCodeURL(state()),
client: newRestyClient(),
}
}
func (o *Oauth2) GetAuthCodeURL() (string, error) {
if o.AuthCodeURL == "" {
return "", fmt.Errorf("unable to retrieve auth code url")
}
return o.AuthCodeURL, nil
}
func (o *Oauth2) GetUserInfo(ctx context.Context, codeFromRedirect string) (*UserInfo, error) {
oauth2Token, err := o.config.Exchange(ctx, codeFromRedirect)
if err != nil {
o.logger.Error().Err(err).Msg("Error getting user infoFailed to exchange token")
return nil, err
}
userInfo, err := o.provider.UserInfo(ctx, oauth2.StaticTokenSource(oauth2Token))
if err != nil {
o.logger.Error().Err(err).Msg("Failed to get userinfo")
return nil, err
}
return &UserInfo{oauth2Token, userInfo}, nil
}
func (o *Oauth2) GetUserProfile(t oauth2.Token) (*UserProfile, error) {
claims := make(map[string]interface{})
if er := o.provider.Claims(&claims); er != nil {
return nil, er
}
url, ok := claims["userinfo_endpoint"]
if !ok {
return nil, fmt.Errorf("userinfo endpoint not found")
}
// Create a Resty Client
resp, err := o.client.R().
SetHeader("Accept", "application/json").
SetAuthToken(t.AccessToken).
Get(url.(string))
if err != nil {
return nil, err
}
var user UserProfile
unmarshalError := json.Unmarshal(resp.Body(), &user)
if unmarshalError != nil {
return nil, unmarshalError
}
return &user, nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment