mirror of
https://github.com/gravitl/netmaker.git
synced 2025-09-08 22:24:17 +08:00
use github apis to fetch user email
This commit is contained in:
parent
8308c2f40c
commit
ebce98448c
3 changed files with 110 additions and 35 deletions
|
@ -138,16 +138,17 @@ type UserGroup struct {
|
|||
|
||||
// User struct - struct for Users
|
||||
type User struct {
|
||||
UserName string `json:"username" bson:"username" validate:"min=3,max=40,in_charset|email"`
|
||||
Password string `json:"password" bson:"password" validate:"required,min=5"`
|
||||
IsAdmin bool `json:"isadmin" bson:"isadmin"` // deprecated
|
||||
IsSuperAdmin bool `json:"issuperadmin"` // deprecated
|
||||
RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` // deprecated
|
||||
AuthType AuthType `json:"auth_type"`
|
||||
UserGroups map[UserGroupID]struct{} `json:"user_group_ids"`
|
||||
PlatformRoleID UserRoleID `json:"platform_role_id"`
|
||||
NetworkRoles map[NetworkID]map[UserRoleID]struct{} `json:"network_roles"`
|
||||
LastLoginTime time.Time `json:"last_login_time"`
|
||||
UserName string `json:"username" bson:"username" validate:"min=3,max=40,in_charset|email"`
|
||||
ExternalProviderID string `json:"external_provider_id"`
|
||||
Password string `json:"password" bson:"password" validate:"required,min=5"`
|
||||
IsAdmin bool `json:"isadmin" bson:"isadmin"` // deprecated
|
||||
IsSuperAdmin bool `json:"issuperadmin"` // deprecated
|
||||
RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` // deprecated
|
||||
AuthType AuthType `json:"auth_type"`
|
||||
UserGroups map[UserGroupID]struct{} `json:"user_group_ids"`
|
||||
PlatformRoleID UserRoleID `json:"platform_role_id"`
|
||||
NetworkRoles map[NetworkID]map[UserRoleID]struct{} `json:"network_roles"`
|
||||
LastLoginTime time.Time `json:"last_login_time"`
|
||||
}
|
||||
|
||||
type ReturnUserWithRolesAndGroups struct {
|
||||
|
|
|
@ -3,6 +3,7 @@ package auth
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
@ -60,7 +61,7 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) {
|
|||
var content, err = getAzureUserInfo(rState, rCode)
|
||||
if err != nil {
|
||||
logger.Log(1, "error when getting user info from azure:", err.Error())
|
||||
if strings.Contains(err.Error(), "invalid oauth state") {
|
||||
if strings.Contains(err.Error(), "invalid oauth state") || strings.Contains(err.Error(), "failed to fetch user email from SSO state") {
|
||||
handleOauthNotValid(w)
|
||||
return
|
||||
}
|
||||
|
@ -74,12 +75,23 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) {
|
|||
inviteExists = true
|
||||
}
|
||||
// check if user approval is already pending
|
||||
if !inviteExists && logic.IsPendingUser(content.UserPrincipalName) {
|
||||
if !inviteExists && logic.IsPendingUser(content.Email) {
|
||||
handleOauthUserSignUpApprovalPending(w)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = logic.GetUser(content.UserPrincipalName)
|
||||
// if user exists with provider ID, convert them into email ID
|
||||
user, err := logic.GetUser(content.UserPrincipalName)
|
||||
if err == nil {
|
||||
_, err := logic.GetUser(content.Email)
|
||||
if err != nil {
|
||||
user.UserName = content.Email
|
||||
user.ExternalProviderID = content.UserPrincipalName
|
||||
database.DeleteRecord(database.USERS_TABLE_NAME, content.UserPrincipalName)
|
||||
d, _ := json.Marshal(user)
|
||||
database.Insert(user.UserName, string(d), database.USERS_TABLE_NAME)
|
||||
}
|
||||
}
|
||||
_, err = logic.GetUser(content.Email)
|
||||
if err != nil {
|
||||
if database.IsEmptyRecord(err) { // user must not exist, so try to make one
|
||||
if inviteExists {
|
||||
|
@ -89,20 +101,20 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) {
|
|||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
user.UserName = content.UserPrincipalName // override username with azure id
|
||||
user.ExternalProviderID = content.UserPrincipalName
|
||||
if err = logic.CreateUser(&user); err != nil {
|
||||
handleSomethingWentWrong(w)
|
||||
return
|
||||
}
|
||||
logic.DeleteUserInvite(content.Email)
|
||||
logic.DeletePendingUser(content.UserPrincipalName)
|
||||
logic.DeletePendingUser(content.Email)
|
||||
} else {
|
||||
if !isEmailAllowed(content.UserPrincipalName) {
|
||||
if !isEmailAllowed(content.Email) {
|
||||
handleOauthUserNotAllowedToSignUp(w)
|
||||
return
|
||||
}
|
||||
err = logic.InsertPendingUser(&models.User{
|
||||
UserName: content.UserPrincipalName,
|
||||
UserName: content.Email,
|
||||
})
|
||||
if err != nil {
|
||||
handleSomethingWentWrong(w)
|
||||
|
@ -116,7 +128,7 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
}
|
||||
user, err := logic.GetUser(content.UserPrincipalName)
|
||||
user, err = logic.GetUser(content.Email)
|
||||
if err != nil {
|
||||
handleOauthUserNotFound(w)
|
||||
return
|
||||
|
@ -136,7 +148,7 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
// send a netmaker jwt token
|
||||
var authRequest = models.UserAuthParams{
|
||||
UserName: content.UserPrincipalName,
|
||||
UserName: content.Email,
|
||||
Password: newPass,
|
||||
}
|
||||
|
||||
|
@ -146,8 +158,8 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
logger.Log(1, "completed azure OAuth sigin in for", content.UserPrincipalName)
|
||||
http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?login="+jwt+"&user="+content.UserPrincipalName, http.StatusPermanentRedirect)
|
||||
logger.Log(1, "completed azure OAuth sigin in for", content.Email)
|
||||
http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?login="+jwt+"&user="+content.Email, http.StatusPermanentRedirect)
|
||||
}
|
||||
|
||||
func getAzureUserInfo(state string, code string) (*OAuthUser, error) {
|
||||
|
@ -187,6 +199,10 @@ func getAzureUserInfo(state string, code string) (*OAuthUser, error) {
|
|||
if userInfo.Email == "" {
|
||||
userInfo.Email = getUserEmailFromClaims(token.AccessToken)
|
||||
}
|
||||
if userInfo.Email == "" {
|
||||
err = errors.New("failed to fetch user email from SSO state")
|
||||
return userInfo, err
|
||||
}
|
||||
return userInfo, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package auth
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
@ -60,7 +61,7 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) {
|
|||
var content, err = getGithubUserInfo(rState, rCode)
|
||||
if err != nil {
|
||||
logger.Log(1, "error when getting user info from github:", err.Error())
|
||||
if strings.Contains(err.Error(), "invalid oauth state") {
|
||||
if strings.Contains(err.Error(), "invalid oauth state") || strings.Contains(err.Error(), "failed to fetch user email from SSO state") {
|
||||
handleOauthNotValid(w)
|
||||
return
|
||||
}
|
||||
|
@ -74,11 +75,25 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) {
|
|||
inviteExists = true
|
||||
}
|
||||
// check if user approval is already pending
|
||||
if !inviteExists && logic.IsPendingUser(content.Login) {
|
||||
if !inviteExists && logic.IsPendingUser(content.Email) {
|
||||
handleOauthUserSignUpApprovalPending(w)
|
||||
return
|
||||
}
|
||||
_, err = logic.GetUser(content.Login)
|
||||
// if user exists with provider ID, convert them into email ID
|
||||
user, err := logic.GetUser(content.Login)
|
||||
if err == nil {
|
||||
// checks if user exists with email
|
||||
_, err := logic.GetUser(content.Email)
|
||||
if err != nil {
|
||||
user.UserName = content.Email
|
||||
user.ExternalProviderID = content.Login
|
||||
database.DeleteRecord(database.USERS_TABLE_NAME, content.Login)
|
||||
d, _ := json.Marshal(user)
|
||||
database.Insert(user.UserName, string(d), database.USERS_TABLE_NAME)
|
||||
}
|
||||
|
||||
}
|
||||
_, err = logic.GetUser(content.Email)
|
||||
if err != nil {
|
||||
if database.IsEmptyRecord(err) { // user must not exist, so try to make one
|
||||
if inviteExists {
|
||||
|
@ -88,20 +103,20 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) {
|
|||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
user.UserName = content.Login // overrides email with github id
|
||||
user.ExternalProviderID = content.Login
|
||||
if err = logic.CreateUser(&user); err != nil {
|
||||
handleSomethingWentWrong(w)
|
||||
return
|
||||
}
|
||||
logic.DeleteUserInvite(content.Email)
|
||||
logic.DeletePendingUser(content.Login)
|
||||
logic.DeletePendingUser(content.Email)
|
||||
} else {
|
||||
if !isEmailAllowed(content.Login) {
|
||||
if !isEmailAllowed(content.Email) {
|
||||
handleOauthUserNotAllowedToSignUp(w)
|
||||
return
|
||||
}
|
||||
err = logic.InsertPendingUser(&models.User{
|
||||
UserName: content.Login,
|
||||
UserName: content.Email,
|
||||
})
|
||||
if err != nil {
|
||||
handleSomethingWentWrong(w)
|
||||
|
@ -115,7 +130,7 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
}
|
||||
user, err := logic.GetUser(content.Login)
|
||||
user, err = logic.GetUser(content.Email)
|
||||
if err != nil {
|
||||
handleOauthUserNotFound(w)
|
||||
return
|
||||
|
@ -135,7 +150,7 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
// send a netmaker jwt token
|
||||
var authRequest = models.UserAuthParams{
|
||||
UserName: content.Login,
|
||||
UserName: content.Email,
|
||||
Password: newPass,
|
||||
}
|
||||
|
||||
|
@ -145,11 +160,11 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
logger.Log(1, "completed github OAuth sigin in for", content.Login)
|
||||
http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?login="+jwt+"&user="+content.Login, http.StatusPermanentRedirect)
|
||||
logger.Log(1, "completed github OAuth sigin in for", content.Email)
|
||||
http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?login="+jwt+"&user="+content.Email, http.StatusPermanentRedirect)
|
||||
}
|
||||
|
||||
func getGithubUserInfo(state string, code string) (*OAuthUser, error) {
|
||||
func getGithubUserInfo(state, code string) (*OAuthUser, error) {
|
||||
oauth_state_string, isValid := logic.IsStateValid(state)
|
||||
if (!isValid || state != oauth_state_string) && !isStateCached(state) {
|
||||
return nil, fmt.Errorf("invalid oauth state")
|
||||
|
@ -187,7 +202,16 @@ func getGithubUserInfo(state string, code string) (*OAuthUser, error) {
|
|||
}
|
||||
userInfo.AccessToken = string(data)
|
||||
if userInfo.Email == "" {
|
||||
userInfo.Email = getUserEmailFromClaims(token.AccessToken)
|
||||
// if user's email is not made public, get the info from the github emails api
|
||||
logger.Log(0, "=======> fetching user email from github api")
|
||||
userInfo.Email, err = getGithubEmailsInfo(token.AccessToken)
|
||||
if err != nil {
|
||||
logger.Log(0, "failed to fetch user's email from github: ", err.Error())
|
||||
}
|
||||
}
|
||||
if userInfo.Email == "" {
|
||||
err = errors.New("failed to fetch user email from SSO state")
|
||||
return userInfo, err
|
||||
}
|
||||
return userInfo, nil
|
||||
}
|
||||
|
@ -195,3 +219,37 @@ func getGithubUserInfo(state string, code string) (*OAuthUser, error) {
|
|||
func verifyGithubUser(token *oauth2.Token) bool {
|
||||
return token.Valid()
|
||||
}
|
||||
|
||||
func getGithubEmailsInfo(accessToken string) (string, error) {
|
||||
|
||||
var httpClient = &http.Client{}
|
||||
var httpReq, reqErr = http.NewRequest("GET", "https://api.github.com/user/emails", nil)
|
||||
if reqErr != nil {
|
||||
return "", fmt.Errorf("failed to create request to GitHub")
|
||||
}
|
||||
httpReq.Header.Add("Accept", "application/vnd.github.v3+json")
|
||||
httpReq.Header.Set("Authorization", "token "+accessToken)
|
||||
response, err := httpClient.Do(httpReq)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed getting user info: %s", err.Error())
|
||||
}
|
||||
defer response.Body.Close()
|
||||
contents, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed reading response body: %s", err.Error())
|
||||
}
|
||||
|
||||
emailsInfo := []interface{}{}
|
||||
err = json.Unmarshal(contents, &emailsInfo)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, info := range emailsInfo {
|
||||
emailInfoMap := info.(map[string]interface{})
|
||||
if emailInfoMap["primary"].(bool) {
|
||||
return emailInfoMap["email"].(string), nil
|
||||
}
|
||||
|
||||
}
|
||||
return "", errors.New("email not found")
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue