mirror of
https://github.com/gravitl/netmaker.git
synced 2025-10-05 16:57:51 +08:00
Merge pull request #1887 from gravitl/feature_cli_sso
Add headless oauth login endpoint for CLI
This commit is contained in:
96
auth/auth.go
96
auth/auth.go
@@ -6,10 +6,12 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/gravitl/netmaker/logger"
|
||||
"github.com/gravitl/netmaker/logic"
|
||||
"github.com/gravitl/netmaker/logic/pro/netcache"
|
||||
@@ -31,6 +33,7 @@ const (
|
||||
auth_key = "netmaker_auth"
|
||||
user_signin_length = 16
|
||||
node_signin_length = 64
|
||||
headless_signin_length = 32
|
||||
)
|
||||
|
||||
// OAuthUser - generic OAuth strategy user
|
||||
@@ -42,7 +45,10 @@ type OAuthUser struct {
|
||||
AccessToken string `json:"accesstoken" bson:"accesstoken"`
|
||||
}
|
||||
|
||||
var auth_provider *oauth2.Config
|
||||
var (
|
||||
auth_provider *oauth2.Config
|
||||
upgrader = websocket.Upgrader{}
|
||||
)
|
||||
|
||||
func getCurrentAuthFunctions() map[string]interface{} {
|
||||
var authInfo = servercfg.GetAuthProviderInfo()
|
||||
@@ -104,9 +110,17 @@ func HandleAuthCallback(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
state, _ := getStateAndCode(r)
|
||||
_, err := netcache.Get(state) // if in netcache proceeed with node registration login
|
||||
if err == nil || len(state) == node_signin_length || errors.Is(err, netcache.ErrExpired) {
|
||||
logger.Log(0, "proceeding with node SSO callback")
|
||||
HandleNodeSSOCallback(w, r)
|
||||
if err == nil || errors.Is(err, netcache.ErrExpired) {
|
||||
switch len(state) {
|
||||
case node_signin_length:
|
||||
logger.Log(0, "proceeding with node SSO callback")
|
||||
HandleNodeSSOCallback(w, r)
|
||||
case headless_signin_length:
|
||||
logger.Log(0, "proceeding with headless SSO callback")
|
||||
HandleHeadlessSSOCallback(w, r)
|
||||
default:
|
||||
logger.Log(1, "invalid state length: ", fmt.Sprintf("%d", len(state)))
|
||||
}
|
||||
} else { // handle normal login
|
||||
functions[handle_callback].(func(http.ResponseWriter, *http.Request))(w, r)
|
||||
}
|
||||
@@ -146,6 +160,80 @@ func IsOauthUser(user *models.User) error {
|
||||
return bCryptErr
|
||||
}
|
||||
|
||||
// HandleHeadlessSSO - handles the OAuth login flow for headless interfaces such as Netmaker CLI via websocket
|
||||
func HandleHeadlessSSO(w http.ResponseWriter, r *http.Request) {
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
logger.Log(0, "error during connection upgrade for headless sign-in:", err.Error())
|
||||
return
|
||||
}
|
||||
if conn == nil {
|
||||
logger.Log(0, "failed to establish web-socket connection during headless sign-in")
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
req := &netcache.CValue{User: "", Pass: ""}
|
||||
stateStr := logic.RandomString(headless_signin_length)
|
||||
if err = netcache.Set(stateStr, req); err != nil {
|
||||
logger.Log(0, "Failed to process sso request -", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
timeout := make(chan bool, 1)
|
||||
answer := make(chan string, 1)
|
||||
defer close(answer)
|
||||
defer close(timeout)
|
||||
|
||||
if auth_provider == nil {
|
||||
if err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")); err != nil {
|
||||
logger.Log(0, "error during message writing:", err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
redirectUrl = fmt.Sprintf("https://%s/api/oauth/register/%s", servercfg.GetAPIConnString(), stateStr)
|
||||
if err = conn.WriteMessage(websocket.TextMessage, []byte(redirectUrl)); err != nil {
|
||||
logger.Log(0, "error during message writing:", err.Error())
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
cachedReq, err := netcache.Get(stateStr)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "expired") {
|
||||
logger.Log(0, "timeout occurred while waiting for SSO")
|
||||
timeout <- true
|
||||
break
|
||||
}
|
||||
continue
|
||||
} else if cachedReq.Pass != "" {
|
||||
logger.Log(0, "SSO process completed for user ", cachedReq.User)
|
||||
answer <- cachedReq.Pass
|
||||
break
|
||||
}
|
||||
time.Sleep(500) // try it 2 times per second to see if auth is completed
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case result := <-answer:
|
||||
if err = conn.WriteMessage(websocket.TextMessage, []byte(result)); err != nil {
|
||||
logger.Log(0, "Error during message writing:", err.Error())
|
||||
}
|
||||
case <-timeout:
|
||||
logger.Log(0, "Authentication server time out for headless SSO login")
|
||||
if err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")); err != nil {
|
||||
logger.Log(0, "Error during message writing:", err.Error())
|
||||
}
|
||||
}
|
||||
if err = netcache.Del(stateStr); err != nil {
|
||||
logger.Log(0, "failed to remove SSO cache entry", err.Error())
|
||||
}
|
||||
if err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")); err != nil {
|
||||
logger.Log(0, "write close:", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// == private methods ==
|
||||
|
||||
func addUser(email string) error {
|
||||
|
Reference in New Issue
Block a user