Last active
May 27, 2022 09:49
-
-
Save IamNator/53aa880c4dc57c7f1141505e5f831aed to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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