From 7b16213e2289740f8792b91c8d22e808ba6e8f7a Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Sun, 18 Jul 2021 04:48:42 +0530 Subject: [PATCH] Add github login Resolves #18 --- server/constants/env.go | 46 ++++---- server/handlers/oauthCallbackHandler.go | 133 ++++++++++++++++++------ server/handlers/oauthLoginHandler.go | 5 + server/oauth/oauth.go | 25 +++-- server/server.go | 6 ++ 5 files changed, 149 insertions(+), 66 deletions(-) diff --git a/server/constants/env.go b/server/constants/env.go index 30cd5f9..e156b19 100644 --- a/server/constants/env.go +++ b/server/constants/env.go @@ -10,27 +10,27 @@ import ( ) var ( - ENV = "" - DB_TYPE = "" - DB_URL = "" - SMTP_HOST = "" - SMTP_PORT = "" - SENDER_EMAIL = "" - SENDER_PASSWORD = "" - JWT_TYPE = "" - JWT_SECRET = "" - FRONTEND_URL = "" - SERVER_URL = "" - PORT = "8080" - REDIS_URL = "" - IS_PROD = false - COOKIE_NAME = "" - GOOGLE_CLIENT_ID = "" - GOOGLE_CLIENT_SECRET = "" - GITHUB_CLIENT_ID = "" - GITHUB_CLIENT_SECRET = "" - FACEBOOK_CLIENT_ID = "" - FACEBOOK_CLIENT_SECRET = "" + ENV = "" + DB_TYPE = "" + DB_URL = "" + SMTP_HOST = "" + SMTP_PORT = "" + SENDER_EMAIL = "" + SENDER_PASSWORD = "" + JWT_TYPE = "" + JWT_SECRET = "" + FRONTEND_URL = "" + SERVER_URL = "" + PORT = "8080" + REDIS_URL = "" + IS_PROD = false + COOKIE_NAME = "" + GOOGLE_CLIENT_ID = "" + GOOGLE_CLIENT_SECRET = "" + GITHUB_CLIENT_ID = "" + GITHUB_CLIENT_SECRET = "" + // FACEBOOK_CLIENT_ID = "" + // FACEBOOK_CLIENT_SECRET = "" ) func init() { @@ -57,8 +57,8 @@ func init() { GOOGLE_CLIENT_SECRET = os.Getenv("GOOGLE_CLIENT_SECRET") GITHUB_CLIENT_ID = os.Getenv("GITHUB_CLIENT_ID") GITHUB_CLIENT_SECRET = os.Getenv("GITHUB_CLIENT_SECRET") - FACEBOOK_CLIENT_ID = os.Getenv("FACEBOOK_CLIENT_ID") - FACEBOOK_CLIENT_SECRET = os.Getenv("FACEBOOK_CLIENT_SECRET") + // FACEBOOK_CLIENT_ID = os.Getenv("FACEBOOK_CLIENT_ID") + // FACEBOOK_CLIENT_SECRET = os.Getenv("FACEBOOK_CLIENT_SECRET") if ENV == "" { ENV = "production" diff --git a/server/handlers/oauthCallbackHandler.go b/server/handlers/oauthCallbackHandler.go index a5176aa..e74b2e9 100644 --- a/server/handlers/oauthCallbackHandler.go +++ b/server/handlers/oauthCallbackHandler.go @@ -44,43 +44,111 @@ func processGoogleUserInfo(state string, code string, c *gin.Context) error { json.Unmarshal(body, &userRawData) existingUser, err := db.Mgr.GetUserByEmail(userRawData["email"]) - user := db.User{} + user := db.User{ + FirstName: userRawData["given_name"], + LastName: userRawData["family_name"], + Image: userRawData["picture"], + Email: userRawData["email"], + EmailVerifiedAt: time.Now().Unix(), + } if err != nil { // user not registered, register user and generate session token - user = db.User{ - FirstName: userRawData["given_name"], - LastName: userRawData["family_name"], - Image: userRawData["picture"], - Email: userRawData["email"], - EmailVerifiedAt: time.Now().Unix(), - SignupMethod: enum.Google.String(), - } - - user, _ = db.Mgr.SaveUser(user) + user.SignupMethod = enum.Google.String() } else { // user exists in db, check if method was google // if not append google to existing signup method and save it signupMethod := existingUser.SignupMethod if !strings.Contains(signupMethod, enum.Google.String()) { - signupMethod += signupMethod + "," + enum.Google.String() + signupMethod = signupMethod + "," + enum.Google.String() } - user = db.User{ - FirstName: userRawData["given_name"], - LastName: userRawData["family_name"], - Image: userRawData["picture"], - Email: userRawData["email"], - EmailVerifiedAt: time.Now().Unix(), - SignupMethod: signupMethod, - Password: existingUser.Password, - } - - user, _ = db.Mgr.SaveUser(user) - + user.SignupMethod = signupMethod + user.Password = existingUser.Password } + user, _ = db.Mgr.SaveUser(user) userIdStr := fmt.Sprintf("%d", user.ID) + refreshToken, _, _ := utils.CreateAuthToken(utils.UserAuthInfo{ + ID: userIdStr, + Email: user.Email, + }, enum.RefreshToken) + accessToken, _, _ := utils.CreateAuthToken(utils.UserAuthInfo{ + ID: userIdStr, + Email: user.Email, + }, enum.AccessToken) + utils.SetCookie(c, accessToken) + session.SetToken(userIdStr, refreshToken) + return nil +} + +func processGithubUserInfo(state string, code string, c *gin.Context) error { + sessionState := session.GetToken(state) + if sessionState == "" { + return fmt.Errorf("invalid oauth state") + } + session.DeleteToken(sessionState) + token, err := oauth.OAuthProvider.GithubConfig.Exchange(oauth2.NoContext, code) + if err != nil { + return fmt.Errorf("invalid google exchange code: %s", err.Error()) + } + client := http.Client{} + req, err := http.NewRequest("GET", constants.GithubUserInfoURL, nil) + if err != nil { + return fmt.Errorf("error creating github user info request: %s", err.Error()) + } + req.Header = http.Header{ + "Authorization": []string{fmt.Sprintf("token %s", token.AccessToken)}, + } + + response, err := client.Do(req) + if err != nil { + return err + } + + defer response.Body.Close() + body, err := ioutil.ReadAll(response.Body) + if err != nil { + return fmt.Errorf("failed to read google response body: %s", err.Error()) + } + + userRawData := make(map[string]string) + json.Unmarshal(body, &userRawData) + + existingUser, err := db.Mgr.GetUserByEmail(userRawData["email"]) + name := strings.Split(userRawData["name"], " ") + firstName := "" + lastName := "" + if len(name) >= 1 && strings.TrimSpace(name[0]) != "" { + firstName = name[0] + } + if len(name) > 1 && strings.TrimSpace(name[1]) != "" { + lastName = name[0] + } + user := db.User{ + FirstName: firstName, + LastName: lastName, + Image: userRawData["avatar_url"], + Email: userRawData["email"], + EmailVerifiedAt: time.Now().Unix(), + } + if err != nil { + // user not registered, register user and generate session token + user.SignupMethod = enum.Github.String() + } else { + // user exists in db, check if method was google + // if not append google to existing signup method and save it + + signupMethod := existingUser.SignupMethod + if !strings.Contains(signupMethod, enum.Github.String()) { + signupMethod = signupMethod + "," + enum.Github.String() + } + user.SignupMethod = signupMethod + user.Password = existingUser.Password + } + + user, _ = db.Mgr.SaveUser(user) + userIdStr := fmt.Sprintf("%d", user.ID) refreshToken, _, _ := utils.CreateAuthToken(utils.UserAuthInfo{ ID: userIdStr, Email: user.Email, @@ -97,13 +165,18 @@ func processGoogleUserInfo(state string, code string, c *gin.Context) error { func HandleOAuthCallback(provider enum.OAuthProvider) gin.HandlerFunc { return func(c *gin.Context) { + var err error if provider == enum.GoogleProvider { - err := processGoogleUserInfo(c.Request.FormValue("state"), c.Request.FormValue("code"), c) - if err != nil { - c.Redirect(http.StatusTemporaryRedirect, constants.FRONTEND_URL+"?error="+err.Error()) - } - - c.Redirect(http.StatusTemporaryRedirect, constants.FRONTEND_URL) + err = processGoogleUserInfo(c.Request.FormValue("state"), c.Request.FormValue("code"), c) } + if provider == enum.GithubProvider { + err = processGithubUserInfo(c.Request.FormValue("state"), c.Request.FormValue("code"), c) + } + + if err != nil { + c.Redirect(http.StatusTemporaryRedirect, constants.FRONTEND_URL+"?error="+err.Error()) + } + + c.Redirect(http.StatusTemporaryRedirect, constants.FRONTEND_URL) } } diff --git a/server/handlers/oauthLoginHandler.go b/server/handlers/oauthLoginHandler.go index 8978595..b55786c 100644 --- a/server/handlers/oauthLoginHandler.go +++ b/server/handlers/oauthLoginHandler.go @@ -20,5 +20,10 @@ func HandleOAuthLogin(provider enum.OAuthProvider) gin.HandlerFunc { url := oauth.OAuthProvider.GoogleConfig.AuthCodeURL(oauthStateString) c.Redirect(http.StatusTemporaryRedirect, url) } + if provider == enum.GithubProvider { + session.SetToken(oauthStateString, enum.Github.String()) + url := oauth.OAuthProvider.GithubConfig.AuthCodeURL(oauthStateString) + c.Redirect(http.StatusTemporaryRedirect, url) + } } } diff --git a/server/oauth/oauth.go b/server/oauth/oauth.go index da8733d..d1c9d1a 100644 --- a/server/oauth/oauth.go +++ b/server/oauth/oauth.go @@ -3,15 +3,14 @@ package oauth import ( "github.com/yauthdev/yauth/server/constants" "golang.org/x/oauth2" - facebookOAuth2 "golang.org/x/oauth2/facebook" githubOAuth2 "golang.org/x/oauth2/github" googleOAuth2 "golang.org/x/oauth2/google" ) type OAuthProviders struct { - GoogleConfig *oauth2.Config - GithubConfig *oauth2.Config - FacebookConfig *oauth2.Config + GoogleConfig *oauth2.Config + GithubConfig *oauth2.Config + // FacebookConfig *oauth2.Config } var OAuthProvider OAuthProviders @@ -27,19 +26,19 @@ func init() { } } if constants.GITHUB_CLIENT_ID != "" && constants.GITHUB_CLIENT_SECRET != "" { - OAuthProvider.GoogleConfig = &oauth2.Config{ + OAuthProvider.GithubConfig = &oauth2.Config{ ClientID: constants.GITHUB_CLIENT_ID, ClientSecret: constants.GITHUB_CLIENT_SECRET, RedirectURL: constants.SERVER_URL + "/callback/github", Endpoint: githubOAuth2.Endpoint, } } - if constants.FACEBOOK_CLIENT_ID != "" && constants.FACEBOOK_CLIENT_SECRET != "" { - OAuthProvider.GoogleConfig = &oauth2.Config{ - ClientID: constants.FACEBOOK_CLIENT_ID, - ClientSecret: constants.FACEBOOK_CLIENT_SECRET, - RedirectURL: constants.SERVER_URL + "/callback/facebook/", - Endpoint: facebookOAuth2.Endpoint, - } - } + // if constants.FACEBOOK_CLIENT_ID != "" && constants.FACEBOOK_CLIENT_SECRET != "" { + // OAuthProvider.FacebookConfig = &oauth2.Config{ + // ClientID: constants.FACEBOOK_CLIENT_ID, + // ClientSecret: constants.FACEBOOK_CLIENT_SECRET, + // RedirectURL: constants.SERVER_URL + "/callback/facebook/", + // Endpoint: facebookOAuth2.Endpoint, + // } + // } } diff --git a/server/server.go b/server/server.go index 2be1e0c..268fc26 100644 --- a/server/server.go +++ b/server/server.go @@ -2,6 +2,7 @@ package main import ( "context" + "log" "github.com/gin-gonic/gin" "github.com/yauthdev/yauth/server/enum" @@ -26,5 +27,10 @@ func main() { r.GET("/login/google", handlers.HandleOAuthLogin(enum.GoogleProvider)) r.GET("/callback/google", handlers.HandleOAuthCallback(enum.GoogleProvider)) } + log.Println(oauth.OAuthProvider.GithubConfig) + if oauth.OAuthProvider.GithubConfig != nil { + r.GET("/login/github", handlers.HandleOAuthLogin(enum.GithubProvider)) + r.GET("/callback/github", handlers.HandleOAuthCallback(enum.GithubProvider)) + } r.Run() }