mirror of
https://github.com/gravitl/netmaker.git
synced 2025-10-06 01:07:41 +08:00
NET-1064: Oauth User SignUp Approval Flow (#2874)
* add pending users api * insert user to pending users on first time oauth login * add pending user check on headless login * fix conflicting apis * no records error * add allowed emails domains for oauth singup to config * check if user is allowed to signup
This commit is contained in:
28
auth/auth.go
28
auth/auth.go
@@ -75,7 +75,7 @@ func InitializeAuthProvider() string {
|
|||||||
if functions == nil {
|
if functions == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
var _, err = fetchPassValue(logic.RandomString(64))
|
var _, err = FetchPassValue(logic.RandomString(64))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Log(0, err.Error())
|
logger.Log(0, err.Error())
|
||||||
return ""
|
return ""
|
||||||
@@ -156,7 +156,7 @@ func HandleAuthLogin(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// IsOauthUser - returns
|
// IsOauthUser - returns
|
||||||
func IsOauthUser(user *models.User) error {
|
func IsOauthUser(user *models.User) error {
|
||||||
var currentValue, err = fetchPassValue("")
|
var currentValue, err = FetchPassValue("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -246,7 +246,7 @@ func addUser(email string) error {
|
|||||||
slog.Error("error checking for existence of admin user during OAuth login for", "email", email, "error", err)
|
slog.Error("error checking for existence of admin user during OAuth login for", "email", email, "error", err)
|
||||||
return err
|
return err
|
||||||
} // generate random password to adapt to current model
|
} // generate random password to adapt to current model
|
||||||
var newPass, fetchErr = fetchPassValue("")
|
var newPass, fetchErr = FetchPassValue("")
|
||||||
if fetchErr != nil {
|
if fetchErr != nil {
|
||||||
return fetchErr
|
return fetchErr
|
||||||
}
|
}
|
||||||
@@ -272,7 +272,7 @@ func addUser(email string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchPassValue(newValue string) (string, error) {
|
func FetchPassValue(newValue string) (string, error) {
|
||||||
|
|
||||||
type valueHolder struct {
|
type valueHolder struct {
|
||||||
Value string `json:"value" bson:"value"`
|
Value string `json:"value" bson:"value"`
|
||||||
@@ -334,3 +334,23 @@ func isStateCached(state string) bool {
|
|||||||
_, err := netcache.Get(state)
|
_, err := netcache.Get(state)
|
||||||
return err == nil || strings.Contains(err.Error(), "expired")
|
return err == nil || strings.Contains(err.Error(), "expired")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isEmailAllowed - checks if email is allowed to signup
|
||||||
|
func isEmailAllowed(email string) bool {
|
||||||
|
allowedDomains := servercfg.GetAllowedEmailDomains()
|
||||||
|
domains := strings.Split(allowedDomains, ",")
|
||||||
|
if len(domains) == 1 && domains[0] == "*" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
emailParts := strings.Split(email, "@")
|
||||||
|
if len(emailParts) < 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
baseDomainOfEmail := emailParts[1]
|
||||||
|
for _, domain := range domains {
|
||||||
|
if domain == baseDomainOfEmail {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@@ -7,6 +7,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gravitl/netmaker/database"
|
||||||
"github.com/gravitl/netmaker/logger"
|
"github.com/gravitl/netmaker/logger"
|
||||||
"github.com/gravitl/netmaker/logic"
|
"github.com/gravitl/netmaker/logic"
|
||||||
"github.com/gravitl/netmaker/models"
|
"github.com/gravitl/netmaker/models"
|
||||||
@@ -60,9 +61,29 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) {
|
|||||||
handleOauthNotConfigured(w)
|
handleOauthNotConfigured(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !isEmailAllowed(content.UserPrincipalName) {
|
||||||
|
handleOauthUserNotAllowedToSignUp(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// check if user approval is already pending
|
||||||
|
if logic.IsPendingUser(content.UserPrincipalName) {
|
||||||
|
handleOauthUserNotAllowed(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
_, err = logic.GetUser(content.UserPrincipalName)
|
_, err = logic.GetUser(content.UserPrincipalName)
|
||||||
if err != nil { // user must not exists, so try to make one
|
if err != nil {
|
||||||
if err = addUser(content.UserPrincipalName); err != nil {
|
if database.IsEmptyRecord(err) { // user must not exist, so try to make one
|
||||||
|
err = logic.InsertPendingUser(&models.User{
|
||||||
|
UserName: content.UserPrincipalName,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
handleSomethingWentWrong(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handleOauthUserNotAllowed(w)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
handleSomethingWentWrong(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,7 +96,7 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) {
|
|||||||
handleOauthUserNotAllowed(w)
|
handleOauthUserNotAllowed(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var newPass, fetchErr = fetchPassValue("")
|
var newPass, fetchErr = FetchPassValue("")
|
||||||
if fetchErr != nil {
|
if fetchErr != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@@ -13,7 +13,8 @@ const oauthNotConfigured = `<!DOCTYPE html><html>
|
|||||||
const userNotAllowed = `<!DOCTYPE html><html>
|
const userNotAllowed = `<!DOCTYPE html><html>
|
||||||
<body>
|
<body>
|
||||||
<h3>Only Admins are allowed to access Dashboard.</h3>
|
<h3>Only Admins are allowed to access Dashboard.</h3>
|
||||||
<p>Non-Admins can access the netmaker networks using <a href="https://docs.netmaker.io/pro/rac.html" target="_blank" rel="noopener">RemoteAccessClient.</a></p>
|
<h3>Furthermore, Admin has to approve your identity to have access to netmaker networks</h3>
|
||||||
|
<p>Once your identity is approved, Non-Admins can access the netmaker networks using <a href="https://docs.netmaker.io/pro/rac.html" target="_blank" rel="noopener">RemoteAccessClient.</a></p>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
`
|
`
|
||||||
@@ -23,6 +24,18 @@ const userNotFound = `<!DOCTYPE html><html>
|
|||||||
</body>
|
</body>
|
||||||
</html>`
|
</html>`
|
||||||
|
|
||||||
|
const somethingwentwrong = `<!DOCTYPE html><html>
|
||||||
|
<body>
|
||||||
|
<h3>Something went wrong. Contact Admin</h3>
|
||||||
|
</body>
|
||||||
|
</html>`
|
||||||
|
|
||||||
|
const notallowedtosignup = `<!DOCTYPE html><html>
|
||||||
|
<body>
|
||||||
|
<h3>You are not allowed to SignUp.</h3>
|
||||||
|
</body>
|
||||||
|
</html>`
|
||||||
|
|
||||||
func handleOauthUserNotFound(response http.ResponseWriter) {
|
func handleOauthUserNotFound(response http.ResponseWriter) {
|
||||||
response.Header().Set("Content-Type", "text/html; charset=utf-8")
|
response.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
response.WriteHeader(http.StatusNotFound)
|
response.WriteHeader(http.StatusNotFound)
|
||||||
@@ -35,9 +48,21 @@ func handleOauthUserNotAllowed(response http.ResponseWriter) {
|
|||||||
response.Write([]byte(userNotAllowed))
|
response.Write([]byte(userNotAllowed))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleOauthUserNotAllowedToSignUp(response http.ResponseWriter) {
|
||||||
|
response.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
response.WriteHeader(http.StatusForbidden)
|
||||||
|
response.Write([]byte(notallowedtosignup))
|
||||||
|
}
|
||||||
|
|
||||||
// handleOauthNotConfigured - returns an appropriate html page when oauth is not configured on netmaker server but an oauth login was attempted
|
// handleOauthNotConfigured - returns an appropriate html page when oauth is not configured on netmaker server but an oauth login was attempted
|
||||||
func handleOauthNotConfigured(response http.ResponseWriter) {
|
func handleOauthNotConfigured(response http.ResponseWriter) {
|
||||||
response.Header().Set("Content-Type", "text/html; charset=utf-8")
|
response.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
response.WriteHeader(http.StatusInternalServerError)
|
response.WriteHeader(http.StatusInternalServerError)
|
||||||
response.Write([]byte(oauthNotConfigured))
|
response.Write([]byte(oauthNotConfigured))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleSomethingWentWrong(response http.ResponseWriter) {
|
||||||
|
response.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
response.WriteHeader(http.StatusInternalServerError)
|
||||||
|
response.Write([]byte(somethingwentwrong))
|
||||||
|
}
|
||||||
|
@@ -7,6 +7,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gravitl/netmaker/database"
|
||||||
"github.com/gravitl/netmaker/logger"
|
"github.com/gravitl/netmaker/logger"
|
||||||
"github.com/gravitl/netmaker/logic"
|
"github.com/gravitl/netmaker/logic"
|
||||||
"github.com/gravitl/netmaker/models"
|
"github.com/gravitl/netmaker/models"
|
||||||
@@ -60,9 +61,29 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) {
|
|||||||
handleOauthNotConfigured(w)
|
handleOauthNotConfigured(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !isEmailAllowed(content.Login) {
|
||||||
|
handleOauthUserNotAllowedToSignUp(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// check if user approval is already pending
|
||||||
|
if logic.IsPendingUser(content.Login) {
|
||||||
|
handleOauthUserNotAllowed(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
_, err = logic.GetUser(content.Login)
|
_, err = logic.GetUser(content.Login)
|
||||||
if err != nil { // user must not exist, so try to make one
|
if err != nil {
|
||||||
if err = addUser(content.Login); err != nil {
|
if database.IsEmptyRecord(err) { // user must not exist, so try to make one
|
||||||
|
err = logic.InsertPendingUser(&models.User{
|
||||||
|
UserName: content.Login,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
handleSomethingWentWrong(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handleOauthUserNotAllowed(w)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
handleSomethingWentWrong(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,7 +96,7 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) {
|
|||||||
handleOauthUserNotAllowed(w)
|
handleOauthUserNotAllowed(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var newPass, fetchErr = fetchPassValue("")
|
var newPass, fetchErr = FetchPassValue("")
|
||||||
if fetchErr != nil {
|
if fetchErr != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@@ -8,6 +8,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gravitl/netmaker/database"
|
||||||
"github.com/gravitl/netmaker/logger"
|
"github.com/gravitl/netmaker/logger"
|
||||||
"github.com/gravitl/netmaker/logic"
|
"github.com/gravitl/netmaker/logic"
|
||||||
"github.com/gravitl/netmaker/models"
|
"github.com/gravitl/netmaker/models"
|
||||||
@@ -62,9 +63,29 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) {
|
|||||||
handleOauthNotConfigured(w)
|
handleOauthNotConfigured(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !isEmailAllowed(content.Email) {
|
||||||
|
handleOauthUserNotAllowedToSignUp(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// check if user approval is already pending
|
||||||
|
if logic.IsPendingUser(content.Email) {
|
||||||
|
handleOauthUserNotAllowed(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
_, err = logic.GetUser(content.Email)
|
_, err = logic.GetUser(content.Email)
|
||||||
if err != nil { // user must not exists, so try to make one
|
if err != nil {
|
||||||
if err = addUser(content.Email); err != nil {
|
if database.IsEmptyRecord(err) { // user must not exist, so try to make one
|
||||||
|
err = logic.InsertPendingUser(&models.User{
|
||||||
|
UserName: content.Email,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
handleSomethingWentWrong(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handleOauthUserNotAllowed(w)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
handleSomethingWentWrong(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,7 +98,7 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) {
|
|||||||
handleOauthUserNotAllowed(w)
|
handleOauthUserNotAllowed(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var newPass, fetchErr = fetchPassValue("")
|
var newPass, fetchErr = FetchPassValue("")
|
||||||
if fetchErr != nil {
|
if fetchErr != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@@ -50,19 +50,24 @@ func HandleHeadlessSSOCallback(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = logic.GetUser(userClaims.getUserName())
|
// check if user approval is already pending
|
||||||
if err != nil { // user must not exists, so try to make one
|
if logic.IsPendingUser(userClaims.getUserName()) {
|
||||||
if err = addUser(userClaims.getUserName()); err != nil {
|
handleOauthUserNotAllowed(w)
|
||||||
logger.Log(1, "could not create new user: ", userClaims.getUserName())
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
user, err := logic.GetUser(userClaims.getUserName())
|
||||||
|
if err != nil {
|
||||||
|
response := returnErrTemplate("", "user not found", state, reqKeyIf)
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
w.Write(response)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
newPass, fetchErr := fetchPassValue("")
|
newPass, fetchErr := FetchPassValue("")
|
||||||
if fetchErr != nil {
|
if fetchErr != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jwt, jwtErr := logic.VerifyAuthRequest(models.UserAuthParams{
|
jwt, jwtErr := logic.VerifyAuthRequest(models.UserAuthParams{
|
||||||
UserName: userClaims.getUserName(),
|
UserName: user.UserName,
|
||||||
Password: newPass,
|
Password: newPass,
|
||||||
})
|
})
|
||||||
if jwtErr != nil {
|
if jwtErr != nil {
|
||||||
|
27
auth/oidc.go
27
auth/oidc.go
@@ -7,6 +7,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coreos/go-oidc/v3/oidc"
|
"github.com/coreos/go-oidc/v3/oidc"
|
||||||
|
"github.com/gravitl/netmaker/database"
|
||||||
"github.com/gravitl/netmaker/logger"
|
"github.com/gravitl/netmaker/logger"
|
||||||
"github.com/gravitl/netmaker/logic"
|
"github.com/gravitl/netmaker/logic"
|
||||||
"github.com/gravitl/netmaker/models"
|
"github.com/gravitl/netmaker/models"
|
||||||
@@ -73,9 +74,29 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) {
|
|||||||
handleOauthNotConfigured(w)
|
handleOauthNotConfigured(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !isEmailAllowed(content.Email) {
|
||||||
|
handleOauthUserNotAllowedToSignUp(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// check if user approval is already pending
|
||||||
|
if logic.IsPendingUser(content.Email) {
|
||||||
|
handleOauthUserNotAllowed(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
_, err = logic.GetUser(content.Email)
|
_, err = logic.GetUser(content.Email)
|
||||||
if err != nil { // user must not exists, so try to make one
|
if err != nil {
|
||||||
if err = addUser(content.Email); err != nil {
|
if database.IsEmptyRecord(err) { // user must not exist, so try to make one
|
||||||
|
err = logic.InsertPendingUser(&models.User{
|
||||||
|
UserName: content.Email,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
handleSomethingWentWrong(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handleOauthUserNotAllowed(w)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
handleSomethingWentWrong(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,7 +109,7 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) {
|
|||||||
handleOauthUserNotAllowed(w)
|
handleOauthUserNotAllowed(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var newPass, fetchErr = fetchPassValue("")
|
var newPass, fetchErr = FetchPassValue("")
|
||||||
if fetchErr != nil {
|
if fetchErr != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@@ -92,6 +92,7 @@ type ServerConfig struct {
|
|||||||
JwtValidityDuration time.Duration `yaml:"jwt_validity_duration"`
|
JwtValidityDuration time.Duration `yaml:"jwt_validity_duration"`
|
||||||
RacAutoDisable bool `yaml:"rac_auto_disable"`
|
RacAutoDisable bool `yaml:"rac_auto_disable"`
|
||||||
CacheEnabled string `yaml:"caching_enabled"`
|
CacheEnabled string `yaml:"caching_enabled"`
|
||||||
|
AllowedEmailDomains string `yaml:"allowed_email_domains"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SQLConfig - Generic SQL Config
|
// SQLConfig - Generic SQL Config
|
||||||
|
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/gravitl/netmaker/auth"
|
"github.com/gravitl/netmaker/auth"
|
||||||
|
"github.com/gravitl/netmaker/database"
|
||||||
"github.com/gravitl/netmaker/logger"
|
"github.com/gravitl/netmaker/logger"
|
||||||
"github.com/gravitl/netmaker/logic"
|
"github.com/gravitl/netmaker/logic"
|
||||||
"github.com/gravitl/netmaker/models"
|
"github.com/gravitl/netmaker/models"
|
||||||
@@ -35,6 +36,11 @@ func userHandlers(r *mux.Router) {
|
|||||||
r.HandleFunc("/api/oauth/callback", auth.HandleAuthCallback).Methods(http.MethodGet)
|
r.HandleFunc("/api/oauth/callback", auth.HandleAuthCallback).Methods(http.MethodGet)
|
||||||
r.HandleFunc("/api/oauth/headless", auth.HandleHeadlessSSO)
|
r.HandleFunc("/api/oauth/headless", auth.HandleHeadlessSSO)
|
||||||
r.HandleFunc("/api/oauth/register/{regKey}", auth.RegisterHostSSO).Methods(http.MethodGet)
|
r.HandleFunc("/api/oauth/register/{regKey}", auth.RegisterHostSSO).Methods(http.MethodGet)
|
||||||
|
r.HandleFunc("/api/users_pending", logic.SecurityCheck(true, http.HandlerFunc(getPendingUsers))).Methods(http.MethodGet)
|
||||||
|
r.HandleFunc("/api/users_pending", logic.SecurityCheck(true, http.HandlerFunc(deleteAllPendingUsers))).Methods(http.MethodDelete)
|
||||||
|
r.HandleFunc("/api/users_pending/user/{username}", logic.SecurityCheck(true, http.HandlerFunc(deletePendingUser))).Methods(http.MethodDelete)
|
||||||
|
r.HandleFunc("/api/users_pending/user/{username}", logic.SecurityCheck(true, http.HandlerFunc(approvePendingUser))).Methods(http.MethodPost)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// swagger:route POST /api/users/adm/authenticate authenticate authenticateUser
|
// swagger:route POST /api/users/adm/authenticate authenticate authenticateUser
|
||||||
@@ -583,3 +589,136 @@ func socketHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
// Start handling the session
|
// Start handling the session
|
||||||
go auth.SessionHandler(conn)
|
go auth.SessionHandler(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// swagger:route GET /api/users_pending user getPendingUsers
|
||||||
|
//
|
||||||
|
// Get all pending users.
|
||||||
|
//
|
||||||
|
// Schemes: https
|
||||||
|
//
|
||||||
|
// Security:
|
||||||
|
// oauth
|
||||||
|
//
|
||||||
|
// Responses:
|
||||||
|
// 200: userBodyResponse
|
||||||
|
func getPendingUsers(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// set header.
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
users, err := logic.ListPendingUsers()
|
||||||
|
if err != nil {
|
||||||
|
logger.Log(0, "failed to fetch users: ", err.Error())
|
||||||
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logic.SortUsers(users[:])
|
||||||
|
logger.Log(2, r.Header.Get("user"), "fetched pending users")
|
||||||
|
json.NewEncoder(w).Encode(users)
|
||||||
|
}
|
||||||
|
|
||||||
|
// swagger:route POST /api/users_pending/user/{username} user approvePendingUser
|
||||||
|
//
|
||||||
|
// approve pending user.
|
||||||
|
//
|
||||||
|
// Schemes: https
|
||||||
|
//
|
||||||
|
// Security:
|
||||||
|
// oauth
|
||||||
|
//
|
||||||
|
// Responses:
|
||||||
|
// 200: userBodyResponse
|
||||||
|
func approvePendingUser(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// set header.
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
var params = mux.Vars(r)
|
||||||
|
username := params["username"]
|
||||||
|
users, err := logic.ListPendingUsers()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Log(0, "failed to fetch users: ", err.Error())
|
||||||
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, user := range users {
|
||||||
|
if user.UserName == username {
|
||||||
|
var newPass, fetchErr = auth.FetchPassValue("")
|
||||||
|
if fetchErr != nil {
|
||||||
|
logic.ReturnErrorResponse(w, r, logic.FormatError(fetchErr, "internal"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = logic.CreateUser(&models.User{
|
||||||
|
UserName: user.UserName,
|
||||||
|
Password: newPass,
|
||||||
|
}); err != nil {
|
||||||
|
logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to create user: %s", err), "internal"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = logic.DeletePendingUser(username)
|
||||||
|
if err != nil {
|
||||||
|
logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to delete pending user: %s", err), "internal"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logic.ReturnSuccessResponse(w, r, "approved "+username)
|
||||||
|
}
|
||||||
|
|
||||||
|
// swagger:route DELETE /api/users_pending/user/{username} user deletePendingUser
|
||||||
|
//
|
||||||
|
// delete pending user.
|
||||||
|
//
|
||||||
|
// Schemes: https
|
||||||
|
//
|
||||||
|
// Security:
|
||||||
|
// oauth
|
||||||
|
//
|
||||||
|
// Responses:
|
||||||
|
// 200: userBodyResponse
|
||||||
|
func deletePendingUser(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// set header.
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
var params = mux.Vars(r)
|
||||||
|
username := params["username"]
|
||||||
|
users, err := logic.ListPendingUsers()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Log(0, "failed to fetch users: ", err.Error())
|
||||||
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, user := range users {
|
||||||
|
if user.UserName == username {
|
||||||
|
err = logic.DeletePendingUser(username)
|
||||||
|
if err != nil {
|
||||||
|
logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to delete pending user: %s", err), "internal"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logic.ReturnSuccessResponse(w, r, "deleted pending "+username)
|
||||||
|
}
|
||||||
|
|
||||||
|
// swagger:route DELETE /api/users_pending/{username}/pending user deleteAllPendingUsers
|
||||||
|
//
|
||||||
|
// delete all pending users.
|
||||||
|
//
|
||||||
|
// Schemes: https
|
||||||
|
//
|
||||||
|
// Security:
|
||||||
|
// oauth
|
||||||
|
//
|
||||||
|
// Responses:
|
||||||
|
// 200: userBodyResponse
|
||||||
|
func deleteAllPendingUsers(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// set header.
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
err := database.DeleteAllRecords(database.PENDING_USERS_TABLE_NAME)
|
||||||
|
if err != nil {
|
||||||
|
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("failed to delete all pending users "+err.Error()), "internal"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logic.ReturnSuccessResponse(w, r, "cleared all pending users")
|
||||||
|
}
|
||||||
|
@@ -61,7 +61,8 @@ const (
|
|||||||
ENROLLMENT_KEYS_TABLE_NAME = "enrollmentkeys"
|
ENROLLMENT_KEYS_TABLE_NAME = "enrollmentkeys"
|
||||||
// HOST_ACTIONS_TABLE_NAME - table name for enrollmentkeys
|
// HOST_ACTIONS_TABLE_NAME - table name for enrollmentkeys
|
||||||
HOST_ACTIONS_TABLE_NAME = "hostactions"
|
HOST_ACTIONS_TABLE_NAME = "hostactions"
|
||||||
|
// PENDING_USERS_TABLE_NAME - table name for pending users
|
||||||
|
PENDING_USERS_TABLE_NAME = "pending_users"
|
||||||
// == ERROR CONSTS ==
|
// == ERROR CONSTS ==
|
||||||
// NO_RECORD - no singular result found
|
// NO_RECORD - no singular result found
|
||||||
NO_RECORD = "no result found"
|
NO_RECORD = "no result found"
|
||||||
@@ -144,6 +145,7 @@ func createTables() {
|
|||||||
CreateTable(HOSTS_TABLE_NAME)
|
CreateTable(HOSTS_TABLE_NAME)
|
||||||
CreateTable(ENROLLMENT_KEYS_TABLE_NAME)
|
CreateTable(ENROLLMENT_KEYS_TABLE_NAME)
|
||||||
CreateTable(HOST_ACTIONS_TABLE_NAME)
|
CreateTable(HOST_ACTIONS_TABLE_NAME)
|
||||||
|
CreateTable(PENDING_USERS_TABLE_NAME)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateTable(tableName string) error {
|
func CreateTable(tableName string) error {
|
||||||
|
@@ -106,7 +106,6 @@ func VerifyUserToken(tokenString string) (username string, issuperadmin, isadmin
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", false, false, err
|
return "", false, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.UserName != "" {
|
if user.UserName != "" {
|
||||||
return user.UserName, user.IsSuperAdmin, user.IsAdmin, nil
|
return user.UserName, user.IsSuperAdmin, user.IsAdmin, nil
|
||||||
}
|
}
|
||||||
|
@@ -75,3 +75,47 @@ func GetSuperAdmin() (models.ReturnUser, error) {
|
|||||||
}
|
}
|
||||||
return models.ReturnUser{}, errors.New("superadmin not found")
|
return models.ReturnUser{}, errors.New("superadmin not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func InsertPendingUser(u *models.User) error {
|
||||||
|
data, err := json.Marshal(u)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return database.Insert(u.UserName, string(data), database.PENDING_USERS_TABLE_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeletePendingUser(username string) error {
|
||||||
|
return database.DeleteRecord(database.PENDING_USERS_TABLE_NAME, username)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsPendingUser(username string) bool {
|
||||||
|
records, err := database.FetchRecords(database.PENDING_USERS_TABLE_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
|
||||||
|
}
|
||||||
|
for _, record := range records {
|
||||||
|
u := models.ReturnUser{}
|
||||||
|
err := json.Unmarshal([]byte(record), &u)
|
||||||
|
if err == nil && u.UserName == username {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListPendingUsers() ([]models.ReturnUser, error) {
|
||||||
|
pendingUsers := []models.ReturnUser{}
|
||||||
|
records, err := database.FetchRecords(database.PENDING_USERS_TABLE_NAME)
|
||||||
|
if err != nil && !database.IsEmptyRecord(err) {
|
||||||
|
return pendingUsers, err
|
||||||
|
}
|
||||||
|
for _, record := range records {
|
||||||
|
u := models.ReturnUser{}
|
||||||
|
err = json.Unmarshal([]byte(record), &u)
|
||||||
|
if err == nil {
|
||||||
|
pendingUsers = append(pendingUsers, u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pendingUsers, nil
|
||||||
|
}
|
||||||
|
@@ -53,6 +53,8 @@ TELEMETRY=on
|
|||||||
# OAuth section
|
# OAuth section
|
||||||
#
|
#
|
||||||
###
|
###
|
||||||
|
# only mentioned domains will be allowded to signup using oauth, by default all domains are allowed
|
||||||
|
ALLOWED_EMAIL_DOMAINS=*
|
||||||
# "<azure-ad|github|google|oidc>"
|
# "<azure-ad|github|google|oidc>"
|
||||||
AUTH_PROVIDER=
|
AUTH_PROVIDER=
|
||||||
# "<client id of your oauth provider>"
|
# "<client id of your oauth provider>"
|
||||||
|
@@ -248,7 +248,7 @@ save_config() { (
|
|||||||
local toCopy=("SERVER_HOST" "MASTER_KEY" "MQ_USERNAME" "MQ_PASSWORD"
|
local toCopy=("SERVER_HOST" "MASTER_KEY" "MQ_USERNAME" "MQ_PASSWORD"
|
||||||
"INSTALL_TYPE" "NODE_ID" "DNS_MODE" "NETCLIENT_AUTO_UPDATE" "API_PORT"
|
"INSTALL_TYPE" "NODE_ID" "DNS_MODE" "NETCLIENT_AUTO_UPDATE" "API_PORT"
|
||||||
"CORS_ALLOWED_ORIGIN" "DISPLAY_KEYS" "DATABASE" "SERVER_BROKER_ENDPOINT" "VERBOSITY"
|
"CORS_ALLOWED_ORIGIN" "DISPLAY_KEYS" "DATABASE" "SERVER_BROKER_ENDPOINT" "VERBOSITY"
|
||||||
"DEBUG_MODE" "REST_BACKEND" "DISABLE_REMOTE_IP_CHECK" "TELEMETRY" "AUTH_PROVIDER" "CLIENT_ID" "CLIENT_SECRET"
|
"DEBUG_MODE" "REST_BACKEND" "DISABLE_REMOTE_IP_CHECK" "TELEMETRY" "ALLOWED_EMAIL_DOMAINS" "AUTH_PROVIDER" "CLIENT_ID" "CLIENT_SECRET"
|
||||||
"FRONTEND_URL" "AZURE_TENANT" "OIDC_ISSUER" "EXPORTER_API_PORT" "JWT_VALIDITY_DURATION" "RAC_AUTO_DISABLE" "CACHING_ENABLED")
|
"FRONTEND_URL" "AZURE_TENANT" "OIDC_ISSUER" "EXPORTER_API_PORT" "JWT_VALIDITY_DURATION" "RAC_AUTO_DISABLE" "CACHING_ENABLED")
|
||||||
for name in "${toCopy[@]}"; do
|
for name in "${toCopy[@]}"; do
|
||||||
save_config_item $name "${!name}"
|
save_config_item $name "${!name}"
|
||||||
|
@@ -703,3 +703,14 @@ func GetEmqxAppID() string {
|
|||||||
func GetEmqxAppSecret() string {
|
func GetEmqxAppSecret() string {
|
||||||
return os.Getenv("EMQX_APP_SECRET")
|
return os.Getenv("EMQX_APP_SECRET")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAllowedEmailDomains - gets the allowed email domains for oauth signup
|
||||||
|
func GetAllowedEmailDomains() string {
|
||||||
|
allowedDomains := "*"
|
||||||
|
if os.Getenv("ALLOWED_EMAIL_DOMAINS") != "" {
|
||||||
|
allowedDomains = os.Getenv("ALLOWED_EMAIL_DOMAINS")
|
||||||
|
} else if config.Config.Server.AllowedEmailDomains != "" {
|
||||||
|
allowedDomains = config.Config.Server.AllowedEmailDomains
|
||||||
|
}
|
||||||
|
return allowedDomains
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user