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:
Abhishek K
2024-04-03 11:20:19 +05:30
committed by GitHub
parent 3152c678e0
commit 0d4552db5e
15 changed files with 361 additions and 29 deletions

View File

@@ -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
}

View File

@@ -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
} }

View File

@@ -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))
}

View File

@@ -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
} }

View File

@@ -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
} }

View File

@@ -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 {

View File

@@ -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
} }

View File

@@ -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

View File

@@ -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")
}

View File

@@ -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 {

View File

@@ -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
} }

View File

@@ -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
}

View File

@@ -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>"

View File

@@ -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}"

View File

@@ -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
}