diff --git a/server/go.mod b/server/go.mod index d2d7063..da00d5c 100644 --- a/server/go.mod +++ b/server/go.mod @@ -4,7 +4,6 @@ go 1.16 require ( github.com/99designs/gqlgen v0.13.0 - github.com/coreos/go-oidc/v3 v3.1.0 // indirect github.com/gin-contrib/location v0.0.2 // indirect github.com/gin-gonic/gin v1.7.2 github.com/go-playground/validator/v10 v10.8.0 // indirect diff --git a/server/go.sum b/server/go.sum index de89b22..808720c 100644 --- a/server/go.sum +++ b/server/go.sum @@ -109,8 +109,6 @@ github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/coreos/go-oidc/v3 v3.1.0 h1:6avEvcdvTa1qYsOZ6I5PRkSYHzpTNWgKYmaJfaYbrRw= -github.com/coreos/go-oidc/v3 v3.1.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -739,7 +737,6 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= @@ -1103,8 +1100,6 @@ gopkg.in/readline.v1 v1.0.0-20160726135117-62c6fe619375/go.mod h1:lNEQeAhU009zbR gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI= gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= -gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= -gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= diff --git a/server/handlers/oauthCallback.go b/server/handlers/oauthCallback.go index ff4ec6b..8455bbf 100644 --- a/server/handlers/oauthCallback.go +++ b/server/handlers/oauthCallback.go @@ -1,10 +1,10 @@ package handlers import ( - "context" "encoding/json" "fmt" "io/ioutil" + "log" "net/http" "strings" "time" @@ -15,49 +15,36 @@ import ( "github.com/authorizerdev/authorizer/server/oauth" "github.com/authorizerdev/authorizer/server/session" "github.com/authorizerdev/authorizer/server/utils" - "github.com/coreos/go-oidc/v3/oidc" "github.com/gin-gonic/gin" "golang.org/x/oauth2" ) -// openID providers has common claims so single helper can work for facebook + google -func processOpenIDProvider(code string, oauth2Config *oauth2.Config, oidcProvider *oidc.Provider) (db.User, error) { +func processGoogleUserInfo(code string) (db.User, error) { user := db.User{} - ctx := context.Background() - oauth2Token, err := oauth2Config.Exchange(ctx, code) + token, err := oauth.OAuthProvider.GoogleConfig.Exchange(oauth2.NoContext, code) if err != nil { - return user, fmt.Errorf("invalid exchange code: %s", err.Error()) + return user, fmt.Errorf("invalid google exchange code: %s", err.Error()) } - - // Extract the ID Token from OAuth2 token. - rawIDToken, ok := oauth2Token.Extra("id_token").(string) - if !ok { - return user, fmt.Errorf("error getting id_token") - } - - verifier := oidcProvider.Verifier(&oidc.Config{ClientID: oauth2Config.ClientID}) - - // Parse and verify ID Token payload. - idToken, err := verifier.Verify(ctx, rawIDToken) + client := oauth.OAuthProvider.GoogleConfig.Client(oauth2.NoContext, token) + response, err := client.Get(constants.GoogleUserInfoURL) if err != nil { - return user, fmt.Errorf("error verifying OpenId token: %s", err.Error()) + return user, err } - var claims struct { - Email string `json:"email"` - Picture string `json:"picture"` - GivenName string `json:"given_name"` - FamilyName string `json:"family_name"` - } - if err := idToken.Claims(&claims); err != nil { - return user, fmt.Errorf("error parsing OpenId claims: %s", err.Error()) + defer response.Body.Close() + body, err := ioutil.ReadAll(response.Body) + if err != nil { + return user, fmt.Errorf("failed to read google response body: %s", err.Error()) } + userRawData := make(map[string]string) + json.Unmarshal(body, &userRawData) + user = db.User{ - FirstName: claims.GivenName, - LastName: claims.FamilyName, - Image: claims.Picture, - Email: claims.Email, + FirstName: userRawData["given_name"], + LastName: userRawData["family_name"], + Image: userRawData["picture"], + Email: userRawData["email"], EmailVerifiedAt: time.Now().Unix(), } @@ -66,8 +53,7 @@ func processOpenIDProvider(code string, oauth2Config *oauth2.Config, oidcProvide func processGithubUserInfo(code string) (db.User, error) { user := db.User{} - ctx := context.Background() - token, err := oauth.OAuthProviders.GithubConfig.Exchange(ctx, code) + token, err := oauth.OAuthProvider.GithubConfig.Exchange(oauth2.NoContext, code) if err != nil { return user, fmt.Errorf("invalid github exchange code: %s", err.Error()) } @@ -114,6 +100,48 @@ func processGithubUserInfo(code string) (db.User, error) { return user, nil } +func processFacebookUserInfo(code string) (db.User, error) { + user := db.User{} + token, err := oauth.OAuthProvider.FacebookConfig.Exchange(oauth2.NoContext, code) + if err != nil { + return user, fmt.Errorf("invalid facebook exchange code: %s", err.Error()) + } + client := http.Client{} + req, err := http.NewRequest("GET", constants.FacebookUserInfoURL+token.AccessToken, nil) + if err != nil { + return user, fmt.Errorf("error creating facebook user info request: %s", err.Error()) + } + + response, err := client.Do(req) + if err != nil { + log.Println("err:", err) + return user, err + } + + defer response.Body.Close() + body, err := ioutil.ReadAll(response.Body) + if err != nil { + return user, fmt.Errorf("failed to read facebook response body: %s", err.Error()) + } + + userRawData := make(map[string]interface{}) + json.Unmarshal(body, &userRawData) + + email := fmt.Sprintf("%v", userRawData["email"]) + + picObject := userRawData["picture"].(map[string]interface{})["data"] + picDataObject := picObject.(map[string]interface{}) + user = db.User{ + FirstName: fmt.Sprintf("%v", userRawData["first_name"]), + LastName: fmt.Sprintf("%v", userRawData["last_name"]), + Image: fmt.Sprintf("%v", picDataObject["url"]), + Email: email, + EmailVerifiedAt: time.Now().Unix(), + } + + return user, nil +} + func OAuthCallbackHandler() gin.HandlerFunc { return func(c *gin.Context) { provider := c.Param("oauth_provider") @@ -137,22 +165,15 @@ func OAuthCallbackHandler() gin.HandlerFunc { redirectURL := sessionSplit[1] var err error - var oidcProvider *oidc.Provider - var oauth2Config *oauth2.Config user := db.User{} code := c.Request.FormValue("code") - switch provider { case enum.Google.String(): - oauth2Config = oauth.OAuthProviders.GoogleConfig - oidcProvider = oauth.OIDCProviders.GoogleOIDC - user, err = processOpenIDProvider(code, oauth2Config, oidcProvider) + user, err = processGoogleUserInfo(code) case enum.Github.String(): user, err = processGithubUserInfo(code) case enum.Facebook.String(): - oauth2Config = oauth.OAuthProviders.FacebookConfig - oidcProvider = oauth.OIDCProviders.FacebookOIDC - user, err = processOpenIDProvider(code, oauth2Config, oidcProvider) + user, err = processFacebookUserInfo(code) default: err = fmt.Errorf(`invalid oauth provider`) } diff --git a/server/handlers/oauthLogin.go b/server/handlers/oauthLogin.go index 676e83a..9f50d9f 100644 --- a/server/handlers/oauthLogin.go +++ b/server/handlers/oauthLogin.go @@ -48,47 +48,28 @@ func OAuthLoginHandler() gin.HandlerFunc { oauthStateString := uuid.String() + "___" + redirectURL + "___" + roles provider := c.Param("oauth_provider") - isProviderConfigured := true switch provider { case enum.Google.String(): - if oauth.OAuthProviders.GoogleConfig == nil { - isProviderConfigured = false - break - } session.SetSocailLoginState(oauthStateString, enum.Google.String()) // during the init of OAuthProvider authorizer url might be empty - oauth.OAuthProviders.GoogleConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/google" - url := oauth.OAuthProviders.GoogleConfig.AuthCodeURL(oauthStateString) + oauth.OAuthProvider.GoogleConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/google" + url := oauth.OAuthProvider.GoogleConfig.AuthCodeURL(oauthStateString) c.Redirect(http.StatusTemporaryRedirect, url) case enum.Github.String(): - if oauth.OAuthProviders.GithubConfig == nil { - isProviderConfigured = false - break - } session.SetSocailLoginState(oauthStateString, enum.Github.String()) - oauth.OAuthProviders.GithubConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/github" - url := oauth.OAuthProviders.GithubConfig.AuthCodeURL(oauthStateString) + oauth.OAuthProvider.GithubConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/github" + url := oauth.OAuthProvider.GithubConfig.AuthCodeURL(oauthStateString) c.Redirect(http.StatusTemporaryRedirect, url) case enum.Facebook.String(): - if oauth.OAuthProviders.FacebookConfig == nil { - isProviderConfigured = false - break - } session.SetSocailLoginState(oauthStateString, enum.Facebook.String()) - oauth.OAuthProviders.FacebookConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/facebook" - url := oauth.OAuthProviders.FacebookConfig.AuthCodeURL(oauthStateString) + oauth.OAuthProvider.FacebookConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/facebook" + url := oauth.OAuthProvider.FacebookConfig.AuthCodeURL(oauthStateString) c.Redirect(http.StatusTemporaryRedirect, url) default: c.JSON(422, gin.H{ "message": "Invalid oauth provider", }) } - - if !isProviderConfigured { - c.JSON(422, gin.H{ - "message": "OAuth provider not configured", - }) - } } } diff --git a/server/oauth/oauth.go b/server/oauth/oauth.go index 1bda9b6..e295393 100644 --- a/server/oauth/oauth.go +++ b/server/oauth/oauth.go @@ -1,76 +1,46 @@ package oauth import ( - "context" - "log" - "github.com/authorizerdev/authorizer/server/constants" - "github.com/coreos/go-oidc/v3/oidc" "golang.org/x/oauth2" + facebookOAuth2 "golang.org/x/oauth2/facebook" githubOAuth2 "golang.org/x/oauth2/github" + googleOAuth2 "golang.org/x/oauth2/google" ) -type OAuthProvider struct { +type OAuthProviders struct { GoogleConfig *oauth2.Config GithubConfig *oauth2.Config FacebookConfig *oauth2.Config } -type OIDCProvider struct { - GoogleOIDC *oidc.Provider - FacebookOIDC *oidc.Provider -} - -var ( - OAuthProviders OAuthProvider - OIDCProviders OIDCProvider -) +var OAuthProvider OAuthProviders func InitOAuth() { - ctx := context.Background() if constants.GOOGLE_CLIENT_ID != "" && constants.GOOGLE_CLIENT_SECRET != "" { - provider, err := oidc.NewProvider(ctx, "https://accounts.google.com") - if err != nil { - log.Fatalln("error configuring Google OpenID provider:", err.Error()) - } - - OIDCProviders.GoogleOIDC = provider - OAuthProviders.GoogleConfig = &oauth2.Config{ + OAuthProvider.GoogleConfig = &oauth2.Config{ ClientID: constants.GOOGLE_CLIENT_ID, ClientSecret: constants.GOOGLE_CLIENT_SECRET, RedirectURL: constants.AUTHORIZER_URL + "/oauth_callback/google", - Endpoint: OIDCProviders.GoogleOIDC.Endpoint(), - Scopes: []string{oidc.ScopeOpenID, "email", "profile"}, + Endpoint: googleOAuth2.Endpoint, + Scopes: []string{"https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile"}, } } - - // Github doesn't support OpenID - // https://stackoverflow.com/questions/52157568/what-is-github-well-known-openid-configuration-url/52164558 - - // https://fusionauth.io/docs/v1/tech/identity-providers/openid-connect/github/ - if constants.GITHUB_CLIENT_ID != "" && constants.GITHUB_CLIENT_SECRET != "" { - OAuthProviders.GithubConfig = &oauth2.Config{ + OAuthProvider.GithubConfig = &oauth2.Config{ ClientID: constants.GITHUB_CLIENT_ID, ClientSecret: constants.GITHUB_CLIENT_SECRET, RedirectURL: constants.AUTHORIZER_URL + "/oauth_callback/github", Endpoint: githubOAuth2.Endpoint, } } - if constants.FACEBOOK_CLIENT_ID != "" && constants.FACEBOOK_CLIENT_SECRET != "" { - provider, err := oidc.NewProvider(ctx, "https://www.facebook.com") - if err != nil { - log.Fatalln("error configuring Facebook OpenID provider:", err.Error()) - } - - OIDCProviders.FacebookOIDC = provider - OAuthProviders.FacebookConfig = &oauth2.Config{ + OAuthProvider.FacebookConfig = &oauth2.Config{ ClientID: constants.FACEBOOK_CLIENT_ID, ClientSecret: constants.FACEBOOK_CLIENT_SECRET, RedirectURL: constants.AUTHORIZER_URL + "/oauth_callback/facebook", - Endpoint: OIDCProviders.FacebookOIDC.Endpoint(), - Scopes: []string{oidc.ScopeOpenID, "email", "public_profile"}, + Endpoint: facebookOAuth2.Endpoint, + Scopes: []string{"public_profile", "email"}, } } } diff --git a/server/resolvers/token.go b/server/resolvers/token.go index 23b95ec..1c4d674 100644 --- a/server/resolvers/token.go +++ b/server/resolvers/token.go @@ -84,16 +84,14 @@ func Token(ctx context.Context, roles []string) (*model.AuthResponse, error) { AccessToken: &token, AccessTokenExpiresAt: &expiresAt, User: &model.User{ - ID: userIdStr, - Email: user.Email, - Image: &user.Image, - FirstName: &user.FirstName, - LastName: &user.LastName, - Roles: strings.Split(user.Roles, ","), - CreatedAt: &user.CreatedAt, - UpdatedAt: &user.UpdatedAt, - EmailVerifiedAt: &user.EmailVerifiedAt, - SignupMethod: user.SignupMethod, + ID: userIdStr, + Email: user.Email, + Image: &user.Image, + FirstName: &user.FirstName, + LastName: &user.LastName, + Roles: strings.Split(user.Roles, ","), + CreatedAt: &user.CreatedAt, + UpdatedAt: &user.UpdatedAt, }, } return res, nil