package oauth import ( "context" "encoding/json" "errors" "log" "recipe-manager/config" "recipe-manager/helpers" "recipe-manager/models" "golang.org/x/oauth2" ) type OAuthService interface { AuthURL(state string, stateMap map[string]string) string GetState(state string) (map[string]string, bool) RemoveState(state string) Exchange(ctx context.Context, code string) (*oauth2.Token, error) GetUserInfo(ctx context.Context, token *oauth2.Token) (*models.User, error) RefreshToken(ctx context.Context, token *oauth2.Token) (*oauth2.Token, error) } type oauthService struct { cfg *config.ServerConfig gConfig *oauth2.Config nonce map[string]map[string]string } func NewOAuthService(cfg *config.ServerConfig) OAuthService { clientSecret, err := helpers.GetClientSecret("client_secret.json") if err != nil { log.Fatalf("Unable to get client secret: %v", err) } clientSecret.RedirectURL = cfg.ServerDomain + "/auth/google/callback" return &oauthService{ cfg: cfg, gConfig: clientSecret, nonce: make(map[string]map[string]string), } } func (o *oauthService) AuthURL(state string, stateMap map[string]string) string { if stateMap != nil { o.nonce[state] = stateMap } else { o.nonce[state] = make(map[string]string) } authCodeOptions := []oauth2.AuthCodeOption{ // oauth2.SetAuthURLParam("hd", "forth.co.th"), oauth2.SetAuthURLParam("include_granted_scopes", "true"), oauth2.AccessTypeOffline, } return o.gConfig.AuthCodeURL(state, authCodeOptions...) } func (o *oauthService) GetState(state string) (map[string]string, bool) { val, ok := o.nonce[state] return val, ok } func (o *oauthService) RemoveState(state string) { delete(o.nonce, state) } func (o *oauthService) Exchange(ctx context.Context, code string) (*oauth2.Token, error) { return o.gConfig.Exchange(ctx, code) } func (o *oauthService) GetUserInfo(ctx context.Context, token *oauth2.Token) (*models.User, error) { client := o.gConfig.Client(ctx, token) resp, err := client.Get("https://www.googleapis.com/oauth2/v3/userinfo") if err != nil { return nil, err } defer resp.Body.Close() var userInfo map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&userInfo); err != nil { return nil, err } if userInfo["error"] != nil { return nil, errors.New("error getting user info") } return &models.User{ Name: userInfo["name"].(string), Email: userInfo["email"].(string), Picture: userInfo["picture"].(string), }, nil } func (o *oauthService) RefreshToken(ctx context.Context, token *oauth2.Token) (*oauth2.Token, error) { return o.gConfig.TokenSource(ctx, token).Token() }