From 36d60eba648161fc189683d417094e8c5ed0afe5 Mon Sep 17 00:00:00 2001 From: 0xdcarns Date: Fri, 22 Oct 2021 13:12:03 -0400 Subject: [PATCH] github auth added --- auth/auth.go | 21 +++++--- auth/github.go | 129 +++++++++++++++++++++++++++++++++++++++++++++++++ auth/google.go | 16 ++++-- logic/auth.go | 2 + 4 files changed, 156 insertions(+), 12 deletions(-) create mode 100644 auth/github.go diff --git a/auth/auth.go b/auth/auth.go index eb16e73f..f6fa331d 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -8,6 +8,7 @@ import ( "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/models" "github.com/gravitl/netmaker/servercfg" + "golang.org/x/crypto/bcrypt" "golang.org/x/oauth2" ) @@ -27,11 +28,6 @@ const ( var oauth_state_string = "netmaker-oauth-state" // should be set randomly each provider login var auth_provider *oauth2.Config -type OauthUser struct { - Email string `json:"email" bson:"email"` - AccessToken string `json:"accesstoken" bson:"accesstoken"` -} - func getCurrentAuthFunctions() map[string]interface{} { var authInfo = servercfg.GetAuthProviderInfo() var authProvider = authInfo[0] @@ -41,7 +37,7 @@ func getCurrentAuthFunctions() map[string]interface{} { case azure_ad_provider_name: return google_functions case github_provider_name: - return google_functions + return github_functions default: return nil } @@ -55,6 +51,7 @@ func InitializeAuthProvider() string { } var _, err = fetchPassValue(logic.RandomString(64)) if err != nil { + logic.Log(err.Error(), 0) return "" } var currentFrontendURL = servercfg.GetFrontendURL() @@ -84,6 +81,16 @@ func HandleAuthLogin(w http.ResponseWriter, r *http.Request) { functions[handle_login].(func(http.ResponseWriter, *http.Request))(w, r) } +// IsOauthUser - returns +func IsOauthUser(user *models.User) error { + var currentValue, err = fetchPassValue("") + if err != nil { + return err + } + var bCryptErr = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(currentValue)) + return bCryptErr +} + // == private methods == func addUser(email string) error { @@ -108,7 +115,7 @@ func addUser(email string) error { } } else { // otherwise add to db as admin..? // TODO: add ability to add users with preemptive permissions - newUser.IsAdmin = true + newUser.IsAdmin = false if newUser, err = logic.CreateUser(newUser); err != nil { logic.Log("error creating user, "+email+", user not added", 1) } else { diff --git a/auth/github.go b/auth/github.go new file mode 100644 index 00000000..a8d0a371 --- /dev/null +++ b/auth/github.go @@ -0,0 +1,129 @@ +package auth + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + + "github.com/gravitl/netmaker/logic" + "github.com/gravitl/netmaker/models" + "github.com/gravitl/netmaker/servercfg" + "golang.org/x/oauth2" + "golang.org/x/oauth2/github" +) + +var github_functions = map[string]interface{}{ + init_provider: initGithub, + get_user_info: getGithubUserInfo, + handle_callback: handleGithubCallback, + handle_login: handleGithubLogin, + verify_user: verifyGithubUser, +} + +type githubOauthUser struct { + Login string `json:"login" bson:"login"` + AccessToken string `json:"accesstoken" bson:"accesstoken"` +} + +// == handle google authentication here == + +func initGithub(redirectURL string, clientID string, clientSecret string) { + auth_provider = &oauth2.Config{ + RedirectURL: redirectURL, + ClientID: clientID, + ClientSecret: clientSecret, + Scopes: []string{}, + Endpoint: github.Endpoint, + } +} + +func handleGithubLogin(w http.ResponseWriter, r *http.Request) { + oauth_state_string = logic.RandomString(16) + if auth_provider == nil && servercfg.GetFrontendURL() != "" { + http.Redirect(w, r, servercfg.GetFrontendURL()+"?error=callback-error", http.StatusTemporaryRedirect) + return + } else if auth_provider == nil { + fmt.Fprintf(w, "%s", []byte("no frontend URL was provided and an OAuth login was attempted\nplease reconfigure server to use OAuth or use basic credentials")) + return + } + var url = auth_provider.AuthCodeURL(oauth_state_string) + http.Redirect(w, r, url, http.StatusTemporaryRedirect) +} + +func handleGithubCallback(w http.ResponseWriter, r *http.Request) { + + var content, err = getGithubUserInfo(r.URL.Query().Get("state"), r.URL.Query().Get("code")) + if err != nil { + logic.Log("error when getting user info from github: "+err.Error(), 1) + http.Redirect(w, r, servercfg.GetFrontendURL()+"?oauth=callback-error", http.StatusTemporaryRedirect) + return + } + _, err = logic.GetUser(content.Login) + if err != nil { // user must not exist, so try to make one + if err = addUser(content.Login); err != nil { + return + } + } + var newPass, fetchErr = fetchPassValue("") + if fetchErr != nil { + return + } + // send a netmaker jwt token + var authRequest = models.UserAuthParams{ + UserName: content.Login, + Password: newPass, + } + + var jwt, jwtErr = logic.VerifyAuthRequest(authRequest) + if jwtErr != nil { + logic.Log("could not parse jwt for user "+authRequest.UserName, 1) + return + } + + logic.Log("completed github oauth sigin in for "+content.Login, 0) + http.Redirect(w, r, servercfg.GetFrontendURL()+"?login="+jwt+"&email="+content.Login, http.StatusPermanentRedirect) +} + +func getGithubUserInfo(state string, code string) (*githubOauthUser, error) { + if state != oauth_state_string { + return nil, fmt.Errorf("invalid oauth state") + } + var token, err = auth_provider.Exchange(oauth2.NoContext, code) + if err != nil { + return nil, fmt.Errorf("code exchange failed: %s", err.Error()) + } + if !token.Valid() { + return nil, fmt.Errorf("GitHub code exchange yielded invalid token") + } + var data []byte + data, err = json.Marshal(token) + if err != nil { + return nil, fmt.Errorf("failed to convert token to json: %s", err.Error()) + } + var httpClient = &http.Client{} + var httpReq, reqErr = http.NewRequest("GET", "https://api.github.com/user", nil) + if reqErr != nil { + return nil, fmt.Errorf("failed to create request to GitHub") + } + httpReq.Header.Set("Authorization", "token "+token.AccessToken) + response, err := httpClient.Do(httpReq) + if err != nil { + return nil, fmt.Errorf("failed getting user info: %s", err.Error()) + } + defer response.Body.Close() + contents, err := ioutil.ReadAll(response.Body) + if err != nil { + return nil, fmt.Errorf("failed reading response body: %s", err.Error()) + } + var userInfo = &githubOauthUser{} + if err = json.Unmarshal(contents, userInfo); err != nil { + return nil, fmt.Errorf("failed parsing email from response data: %s", err.Error()) + } + userInfo.AccessToken = string(data) + return userInfo, nil +} + +func verifyGithubUser(token *oauth2.Token) bool { + return token.Valid() +} diff --git a/auth/google.go b/auth/google.go index 3c47bb3c..0a5d8a26 100644 --- a/auth/google.go +++ b/auth/google.go @@ -15,12 +15,17 @@ import ( var google_functions = map[string]interface{}{ init_provider: initGoogle, - get_user_info: getUserInfo, + get_user_info: getGoogleUserInfo, handle_callback: handleGoogleCallback, handle_login: handleGoogleLogin, verify_user: verifyGoogleUser, } +type googleOauthUser struct { + Email string `json:"email" bson:"email"` + AccessToken string `json:"accesstoken" bson:"accesstoken"` +} + // == handle google authentication here == func initGoogle(redirectURL string, clientID string, clientSecret string) { @@ -37,6 +42,7 @@ func handleGoogleLogin(w http.ResponseWriter, r *http.Request) { oauth_state_string = logic.RandomString(16) if auth_provider == nil && servercfg.GetFrontendURL() != "" { http.Redirect(w, r, servercfg.GetFrontendURL()+"?oauth=callback-error", http.StatusTemporaryRedirect) + return } else if auth_provider == nil { fmt.Fprintf(w, "%s", []byte("no frontend URL was provided and an OAuth login was attempted\nplease reconfigure server to use OAuth or use basic credentials")) return @@ -47,9 +53,9 @@ func handleGoogleLogin(w http.ResponseWriter, r *http.Request) { func handleGoogleCallback(w http.ResponseWriter, r *http.Request) { - var content, err = getUserInfo(r.FormValue("state"), r.FormValue("code")) + var content, err = getGoogleUserInfo(r.FormValue("state"), r.FormValue("code")) if err != nil { - fmt.Println(err.Error()) + logic.Log("error when getting user info from google: "+err.Error(), 1) http.Redirect(w, r, servercfg.GetFrontendURL()+"?oauth=callback-error", http.StatusTemporaryRedirect) return } @@ -79,7 +85,7 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, servercfg.GetFrontendURL()+"?login="+jwt+"&email="+content.Email, http.StatusPermanentRedirect) } -func getUserInfo(state string, code string) (*OauthUser, error) { +func getGoogleUserInfo(state string, code string) (*googleOauthUser, error) { if state != oauth_state_string { return nil, fmt.Errorf("invalid oauth state") } @@ -101,7 +107,7 @@ func getUserInfo(state string, code string) (*OauthUser, error) { if err != nil { return nil, fmt.Errorf("failed reading response body: %s", err.Error()) } - var userInfo = &OauthUser{} + var userInfo = &googleOauthUser{} if err = json.Unmarshal(contents, userInfo); err != nil { return nil, fmt.Errorf("failed parsing email from response data: %s", err.Error()) } diff --git a/logic/auth.go b/logic/auth.go index 41e82e43..3d3ae4d0 100644 --- a/logic/auth.go +++ b/logic/auth.go @@ -233,6 +233,8 @@ func FetchAuthSecret(key string, secret string) (string, error) { if err != nil { if err = database.Insert(key, secret, database.GENERATED_TABLE_NAME); err != nil { return "", err + } else { + return secret, nil } } return record, nil