Browse Source

Merge branch 'NET-1950' into NET-1991

# Conflicts:
#	controllers/user.go
Vishal Dalwadi 4 months ago
parent
commit
cc64e634df

+ 2 - 2
auth/host_session.go

@@ -67,7 +67,7 @@ func SessionHandler(conn *websocket.Conn) {
 	if len(registerMessage.User) > 0 { // handle basic auth
 		logger.Log(0, "user registration attempted with host:", registerMessage.RegisterHost.Name, "user:", registerMessage.User)
 
-		if !servercfg.IsBasicAuthEnabled() {
+		if !logic.IsBasicAuthEnabled() {
 			err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
 			if err != nil {
 				logger.Log(0, "error during message writing:", err.Error())
@@ -207,7 +207,7 @@ func SessionHandler(conn *websocket.Conn) {
 				netsToAdd = append(netsToAdd, newNet)
 			}
 		}
-		server := servercfg.GetServerInfo()
+		server := logic.GetServerInfo()
 		server.TrafficKey = key
 		result.Host.HostPass = ""
 		response := models.RegisterResponse{

+ 0 - 1
config/config.go

@@ -76,7 +76,6 @@ type ServerConfig struct {
 	NetmakerTenantID           string        `yaml:"netmaker_tenant_id"`
 	IsPro                      string        `yaml:"is_ee" json:"IsEE"`
 	StunPort                   int           `yaml:"stun_port"`
-	StunList                   string        `yaml:"stun_list"`
 	TurnServer                 string        `yaml:"turn_server"`
 	TurnApiServer              string        `yaml:"turn_api_server"`
 	TurnPort                   int           `yaml:"turn_port"`

+ 2 - 0
controllers/controller.go

@@ -3,6 +3,7 @@ package controller
 import (
 	"context"
 	"fmt"
+	"github.com/gravitl/netmaker/db"
 	"net/http"
 	"os"
 	"strings"
@@ -18,6 +19,7 @@ import (
 
 // HttpMiddlewares - middleware functions for REST interactions
 var HttpMiddlewares = []mux.MiddlewareFunc{
+	db.Middleware,
 	userMiddleWare,
 }
 

+ 6 - 6
controllers/dns.go

@@ -164,9 +164,9 @@ func createDNS(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 	// check if default domain is appended if not append
-	if servercfg.GetDefaultDomain() != "" &&
-		!strings.HasSuffix(entry.Name, servercfg.GetDefaultDomain()) {
-		entry.Name += "." + servercfg.GetDefaultDomain()
+	if logic.GetDefaultDomain() != "" &&
+		!strings.HasSuffix(entry.Name, logic.GetDefaultDomain()) {
+		entry.Name += "." + logic.GetDefaultDomain()
 	}
 	entry, err = logic.CreateDNS(entry)
 	if err != nil {
@@ -185,7 +185,7 @@ func createDNS(w http.ResponseWriter, r *http.Request) {
 		}
 	}
 
-	if servercfg.GetManageDNS() {
+	if logic.GetManageDNS() {
 		mq.SendDNSSyncByNetwork(netID)
 	}
 
@@ -230,7 +230,7 @@ func deleteDNS(w http.ResponseWriter, r *http.Request) {
 		}
 	}
 
-	if servercfg.GetManageDNS() {
+	if logic.GetManageDNS() {
 		mq.SendDNSSyncByNetwork(netID)
 	}
 
@@ -293,7 +293,7 @@ func pushDNS(w http.ResponseWriter, r *http.Request) {
 func syncDNS(w http.ResponseWriter, r *http.Request) {
 	// Set header
 	w.Header().Set("Content-Type", "application/json")
-	if !servercfg.GetManageDNS() {
+	if !logic.GetManageDNS() {
 		logic.ReturnErrorResponse(
 			w,
 			r,

+ 2 - 1
controllers/enrollmentkeys.go

@@ -160,6 +160,7 @@ func createEnrollmentKey(w http.ResponseWriter, r *http.Request) {
 		enrollmentKeyBody.Unlimited,
 		relayId,
 		false,
+		enrollmentKeyBody.AutoEgress,
 	)
 	if err != nil {
 		logger.Log(0, r.Header.Get("user"), "failed to create enrollment key:", err.Error())
@@ -348,7 +349,7 @@ func handleHostRegister(w http.ResponseWriter, r *http.Request) {
 		}
 	}
 	// ready the response
-	server := servercfg.GetServerInfo()
+	server := logic.GetServerInfo()
 	server.TrafficKey = key
 	response := models.RegisterResponse{
 		ServerConf:    server,

+ 2 - 2
controllers/hosts.go

@@ -209,7 +209,7 @@ func pull(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 	}
-	serverConf := servercfg.GetServerInfo()
+	serverConf := logic.GetServerInfo()
 	key, keyErr := logic.RetrievePublicTrafficKey()
 	if keyErr != nil {
 		logger.Log(0, "error retrieving key:", keyErr.Error())
@@ -230,7 +230,7 @@ func pull(w http.ResponseWriter, r *http.Request) {
 		ChangeDefaultGw:   hPU.ChangeDefaultGw,
 		DefaultGwIp:       hPU.DefaultGwIp,
 		IsInternetGw:      hPU.IsInternetGw,
-		EndpointDetection: servercfg.IsEndpointDetectionEnabled(),
+		EndpointDetection: logic.IsEndpointDetectionEnabled(),
 	}
 
 	logger.Log(1, hostID, "completed a pull")

+ 2 - 2
controllers/migrate.go

@@ -70,7 +70,7 @@ func migrate(w http.ResponseWriter, r *http.Request) {
 				logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 				return
 			}
-			server = servercfg.GetServerInfo()
+			server = logic.GetServerInfo()
 			key, keyErr := logic.RetrievePublicTrafficKey()
 			if keyErr != nil {
 				slog.Error("retrieving traffickey", "error", err)
@@ -134,7 +134,7 @@ func convertLegacyHostNode(legacy models.LegacyNode) (models.Host, models.Node)
 	host := models.Host{}
 	host.ID = uuid.New()
 	host.IPForwarding = models.ParseBool(legacy.IPForwarding)
-	host.AutoUpdate = servercfg.AutoUpdateEnabled()
+	host.AutoUpdate = logic.AutoUpdateEnabled()
 	host.Interface = "netmaker"
 	host.ListenPort = int(legacy.ListenPort)
 	if host.ListenPort == 0 {

+ 1 - 2
controllers/network.go

@@ -588,8 +588,7 @@ func createNetwork(w http.ResponseWriter, r *http.Request) {
 	logic.CreateDefaultNetworkRolesAndGroups(models.NetworkID(network.NetID))
 	logic.CreateDefaultAclNetworkPolicies(models.NetworkID(network.NetID))
 	logic.CreateDefaultTags(models.NetworkID(network.NetID))
-
-	go logic.AddNetworkToAllocatedIpMap(network.NetID)
+	logic.AddNetworkToAllocatedIpMap(network.NetID)
 
 	go func() {
 		defaultHosts := logic.GetDefaultHosts()

+ 1 - 1
controllers/node.go

@@ -477,7 +477,7 @@ func getNode(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 	}
-	server := servercfg.GetServerInfo()
+	server := logic.GetServerInfo()
 	response := models.NodeGet{
 		Node:         node,
 		Host:         *host,

+ 44 - 2
controllers/server.go

@@ -2,6 +2,7 @@ package controller
 
 import (
 	"encoding/json"
+	"errors"
 	"net/http"
 	"os"
 	"strings"
@@ -12,6 +13,7 @@ import (
 	"golang.org/x/exp/slog"
 
 	"github.com/gravitl/netmaker/database"
+	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/mq"
@@ -41,6 +43,10 @@ func serverHandlers(r *mux.Router) {
 	).Methods(http.MethodPost)
 	r.HandleFunc("/api/server/getconfig", allowUsers(http.HandlerFunc(getConfig))).
 		Methods(http.MethodGet)
+	r.HandleFunc("/api/server/settings", allowUsers(http.HandlerFunc(getSettings))).
+		Methods(http.MethodGet)
+	r.HandleFunc("/api/server/settings", logic.SecurityCheck(true, http.HandlerFunc(updateSettings))).
+		Methods(http.MethodPut)
 	r.HandleFunc("/api/server/getserverinfo", logic.SecurityCheck(true, http.HandlerFunc(getServerInfo))).
 		Methods(http.MethodGet)
 	r.HandleFunc("/api/server/status", getStatus).Methods(http.MethodGet)
@@ -207,7 +213,7 @@ func getServerInfo(w http.ResponseWriter, r *http.Request) {
 
 	// get params
 
-	json.NewEncoder(w).Encode(servercfg.GetServerInfo())
+	json.NewEncoder(w).Encode(logic.GetServerInfo())
 	// w.WriteHeader(http.StatusOK)
 }
 
@@ -222,7 +228,7 @@ func getConfig(w http.ResponseWriter, r *http.Request) {
 
 	// get params
 
-	scfg := servercfg.GetServerConfig()
+	scfg := logic.GetServerConfig()
 	scfg.IsPro = "no"
 	if servercfg.IsPro {
 		scfg.IsPro = "yes"
@@ -230,3 +236,39 @@ func getConfig(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(scfg)
 	// w.WriteHeader(http.StatusOK)
 }
+
+// @Summary     Get the server settings
+// @Router      /api/server/settings [get]
+// @Tags        Server
+// @Security    oauth2
+// @Success     200 {object} config.ServerSettings
+func getSettings(w http.ResponseWriter, r *http.Request) {
+	scfg := logic.GetServerSettings()
+	logic.ReturnSuccessResponseWithJson(w, r, scfg, "fetched server settings successfully")
+}
+
+// @Summary     Update the server settings
+// @Router      /api/server/settings [put]
+// @Tags        Server
+// @Security    oauth2
+// @Success     200 {object} config.ServerSettings
+func updateSettings(w http.ResponseWriter, r *http.Request) {
+	var req models.ServerSettings
+
+	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+		logger.Log(0, r.Header.Get("user"), "error decoding request body: ", err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	if !logic.ValidateNewSettings(req) {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("invalid settings"), "badrequest"))
+		return
+	}
+	err := logic.UpsertServerSettings(req)
+	if err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("failed to udpate server settings "+err.Error()), "internal"))
+		return
+	}
+	go mq.PublishPeerUpdate(false)
+	logic.ReturnSuccessResponseWithJson(w, r, req, "updated server settings successfully")
+}

+ 150 - 41
controllers/user.go

@@ -4,9 +4,12 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	"github.com/gravitl/netmaker/schema"
 	"net/http"
 	"reflect"
+	"time"
 
+	"github.com/google/uuid"
 	"github.com/gorilla/mux"
 	"github.com/gorilla/websocket"
 	"github.com/gravitl/netmaker/auth"
@@ -39,11 +42,13 @@ func userHandlers(r *mux.Router) {
 	r.HandleFunc("/api/v1/users", logic.SecurityCheck(false, logic.ContinueIfUserMatch(http.HandlerFunc(getUserV1)))).Methods(http.MethodGet)
 	r.HandleFunc("/api/users", logic.SecurityCheck(true, http.HandlerFunc(getUsers))).Methods(http.MethodGet)
 	r.HandleFunc("/api/v1/users/roles", logic.SecurityCheck(true, http.HandlerFunc(ListRoles))).Methods(http.MethodGet)
-
+	r.HandleFunc("/api/v1/users/access_token", logic.SecurityCheck(true, http.HandlerFunc(createUserAccessToken))).Methods(http.MethodPost)
+	r.HandleFunc("/api/v1/users/access_token", logic.SecurityCheck(true, http.HandlerFunc(getUserAccessTokens))).Methods(http.MethodGet)
+	r.HandleFunc("/api/v1/users/access_token", logic.SecurityCheck(true, http.HandlerFunc(deleteUserAccessTokens))).Methods(http.MethodDelete)
 }
 
 // @Summary     Authenticate a user to retrieve an authorization token
-// @Router      /api/users/adm/authenticate [post]
+// @Router      /api/v1/users/{username}/access_token [post]
 // @Tags        Auth
 // @Accept      json
 // @Param       body body models.UserAuthParams true "Authentication parameters"
@@ -51,24 +56,128 @@ func userHandlers(r *mux.Router) {
 // @Failure     400 {object} models.ErrorResponse
 // @Failure     401 {object} models.ErrorResponse
 // @Failure     500 {object} models.ErrorResponse
-func authenticateUser(response http.ResponseWriter, request *http.Request) {
+func createUserAccessToken(w http.ResponseWriter, r *http.Request) {
 
 	// Auth request consists of Mac Address and Password (from node that is authorizing
 	// in case of Master, auth is ignored and mac is set to "mastermac"
-	var authRequest models.UserAuthParams
-	var errorResponse = models.ErrorResponse{
-		Code: http.StatusInternalServerError, Message: "W1R3: It's not you it's me.",
+	var req schema.UserAccessToken
+
+	err := json.NewDecoder(r.Body).Decode(&req)
+	if err != nil {
+		logger.Log(0, "error decoding request body: ",
+			err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	if req.Name == "" {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("name is required"), "badrequest"))
+		return
+	}
+	if req.UserName == "" {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("username is required"), "badrequest"))
+		return
 	}
 
-	if !servercfg.IsBasicAuthEnabled() {
+	user, err := logic.GetUser(req.UserName)
+	if err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "unauthorized"))
+		return
+	}
+	if logic.IsOauthUser(user) == nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("user is registered via SSO"), "badrequest"))
+		return
+	}
+	req.ID = uuid.New().String()
+	req.CreatedBy = r.Header.Get("user")
+	req.CreatedAt = time.Now()
+	jwt, err := logic.CreateUserAccessJwtToken(user.UserName, user.PlatformRoleID, req.ExpiresAt, req.ID)
+	if jwt == "" {
+		// very unlikely that err is !nil and no jwt returned, but handle it anyways.
 		logic.ReturnErrorResponse(
-			response,
-			request,
-			logic.FormatError(fmt.Errorf("basic auth is disabled"), "badrequest"),
+			w,
+			r,
+			logic.FormatError(errors.New("error creating access token "+err.Error()), "internal"),
 		)
 		return
 	}
+	err = req.Create(r.Context())
+	if err != nil {
+		logic.ReturnErrorResponse(
+			w,
+			r,
+			logic.FormatError(errors.New("error creating access token "+err.Error()), "internal"),
+		)
+		return
+	}
+	logic.ReturnSuccessResponseWithJson(w, r, models.SuccessfulUserLoginResponse{
+		AuthToken: jwt,
+		UserName:  req.UserName,
+	}, "api access token has generated for user "+req.UserName)
+}
+
+// @Summary     Authenticate a user to retrieve an authorization token
+// @Router      /api/v1/users/{username}/access_token [post]
+// @Tags        Auth
+// @Accept      json
+// @Param       body body models.UserAuthParams true "Authentication parameters"
+// @Success     200 {object} models.SuccessResponse
+// @Failure     400 {object} models.ErrorResponse
+// @Failure     401 {object} models.ErrorResponse
+// @Failure     500 {object} models.ErrorResponse
+func getUserAccessTokens(w http.ResponseWriter, r *http.Request) {
+	username := r.URL.Query().Get("username")
+	if username == "" {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("username is required"), "badrequest"))
+		return
+	}
+	logic.ReturnSuccessResponseWithJson(w, r, (&schema.UserAccessToken{UserName: username}).ListByUser(r.Context()), "fetched api access tokens for user "+username)
+}
 
+// @Summary     Authenticate a user to retrieve an authorization token
+// @Router      /api/v1/users/{username}/access_token [post]
+// @Tags        Auth
+// @Accept      json
+// @Param       body body models.UserAuthParams true "Authentication parameters"
+// @Success     200 {object} models.SuccessResponse
+// @Failure     400 {object} models.ErrorResponse
+// @Failure     401 {object} models.ErrorResponse
+// @Failure     500 {object} models.ErrorResponse
+func deleteUserAccessTokens(w http.ResponseWriter, r *http.Request) {
+	id := r.URL.Query().Get("id")
+	if id == "" {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("id is required"), "badrequest"))
+		return
+	}
+
+	err := (&schema.UserAccessToken{ID: id}).Delete(r.Context())
+	if err != nil {
+		logic.ReturnErrorResponse(
+			w,
+			r,
+			logic.FormatError(errors.New("error deleting access token "+err.Error()), "internal"),
+		)
+		return
+	}
+	logic.ReturnSuccessResponseWithJson(w, r, nil, "revoked access token")
+}
+
+// @Summary     Authenticate a user to retrieve an authorization token
+// @Router      /api/users/adm/authenticate [post]
+// @Tags        Auth
+// @Accept      json
+// @Param       body body models.UserAuthParams true "Authentication parameters"
+// @Success     200 {object} models.SuccessResponse
+// @Failure     400 {object} models.ErrorResponse
+// @Failure     401 {object} models.ErrorResponse
+// @Failure     500 {object} models.ErrorResponse
+func authenticateUser(response http.ResponseWriter, request *http.Request) {
+
+	// Auth request consists of Mac Address and Password (from node that is authorizing
+	// in case of Master, auth is ignored and mac is set to "mastermac"
+	var authRequest models.UserAuthParams
+	var errorResponse = models.ErrorResponse{
+		Code: http.StatusInternalServerError, Message: "W1R3: It's not you it's me.",
+	}
 	decoder := json.NewDecoder(request.Body)
 	decoderErr := decoder.Decode(&authRequest)
 	defer request.Body.Close()
@@ -78,43 +187,48 @@ func authenticateUser(response http.ResponseWriter, request *http.Request) {
 		logic.ReturnErrorResponse(response, request, errorResponse)
 		return
 	}
-	if val := request.Header.Get("From-Ui"); val == "true" {
-		// request came from UI, if normal user block Login
-		user, err := logic.GetUser(authRequest.UserName)
-		if err != nil {
-			logger.Log(0, authRequest.UserName, "user validation failed: ",
-				err.Error())
-			logic.ReturnErrorResponse(response, request, logic.FormatError(err, "unauthorized"))
-			return
-		}
-		role, err := logic.GetRole(user.PlatformRoleID)
-		if err != nil {
-			logic.ReturnErrorResponse(response, request, logic.FormatError(errors.New("access denied to dashboard"), "unauthorized"))
-			return
-		}
-		if role.DenyDashboardAccess {
-			logic.ReturnErrorResponse(response, request, logic.FormatError(errors.New("access denied to dashboard"), "unauthorized"))
-			return
-		}
-	}
-
 	user, err := logic.GetUser(authRequest.UserName)
 	if err != nil {
+		logger.Log(0, authRequest.UserName, "user validation failed: ",
+			err.Error())
 		logic.ReturnErrorResponse(response, request, logic.FormatError(err, "unauthorized"))
 		return
 	}
 
+	if logic.IsOauthUser(user) == nil {
+		logic.ReturnErrorResponse(response, request, logic.FormatError(errors.New("user is registered via SSO"), "badrequest"))
+		return
+	}
+
 	if user.AccountDisabled {
 		err = errors.New("user account disabled")
 		logic.ReturnErrorResponse(response, request, logic.FormatError(err, "unauthorized"))
 		return
 	}
 
-	if logic.IsOauthUser(user) == nil {
-		logic.ReturnErrorResponse(response, request, logic.FormatError(errors.New("user is registered via SSO"), "badrequest"))
+	if !user.IsSuperAdmin && !logic.IsBasicAuthEnabled() {
+		logic.ReturnErrorResponse(
+			response,
+			request,
+			logic.FormatError(fmt.Errorf("basic auth is disabled"), "badrequest"),
+		)
 		return
 	}
 
+	if val := request.Header.Get("From-Ui"); val == "true" {
+		// request came from UI, if normal user block Login
+
+		role, err := logic.GetRole(user.PlatformRoleID)
+		if err != nil {
+			logic.ReturnErrorResponse(response, request, logic.FormatError(errors.New("access denied to dashboard"), "unauthorized"))
+			return
+		}
+		if role.DenyDashboardAccess {
+			logic.ReturnErrorResponse(response, request, logic.FormatError(errors.New("access denied to dashboard"), "unauthorized"))
+			return
+		}
+	}
+
 	username := authRequest.UserName
 	jwt, err := logic.VerifyAuthRequest(authRequest)
 	if err != nil {
@@ -156,7 +270,7 @@ func authenticateUser(response http.ResponseWriter, request *http.Request) {
 	response.Write(successJSONResponse)
 
 	go func() {
-		if servercfg.IsPro && servercfg.GetRacAutoDisable() {
+		if servercfg.IsPro && logic.GetRacAutoDisable() {
 			// enable all associeated clients for the user
 			clients, err := logic.GetAllExtClients()
 			if err != nil {
@@ -389,7 +503,7 @@ func createSuperAdmin(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	if !servercfg.IsBasicAuthEnabled() {
+	if !logic.IsBasicAuthEnabled() {
 		logic.ReturnErrorResponse(
 			w,
 			r,
@@ -437,7 +551,7 @@ func transferSuperAdmin(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("only admins can be promoted to superadmin role"), "forbidden"))
 		return
 	}
-	if !servercfg.IsBasicAuthEnabled() {
+	if !logic.IsBasicAuthEnabled() {
 		logic.ReturnErrorResponse(
 			w,
 			r,
@@ -741,17 +855,12 @@ func deleteUser(w http.ResponseWriter, r *http.Request) {
 			return
 		}
 	}
-	success, err := logic.DeleteUser(username)
+	err = logic.DeleteUser(username)
 	if err != nil {
 		logger.Log(0, username,
 			"failed to delete user: ", err.Error())
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
-	} else if !success {
-		err := errors.New("delete unsuccessful")
-		logger.Log(0, username, err.Error())
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
-		return
 	}
 	// check and delete extclient with this ownerID
 	go func() {

+ 7 - 64
database/database.go

@@ -1,18 +1,12 @@
 package database
 
 import (
-	"crypto/rand"
-	"encoding/json"
 	"errors"
 	"sync"
 	"time"
 
-	"github.com/google/uuid"
 	"github.com/gravitl/netmaker/logger"
-	"github.com/gravitl/netmaker/models"
-	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/servercfg"
-	"golang.org/x/crypto/nacl/box"
 )
 
 const (
@@ -25,6 +19,8 @@ const (
 	DELETED_NODES_TABLE_NAME = "deletednodes"
 	// USERS_TABLE_NAME - users table
 	USERS_TABLE_NAME = "users"
+	// ACCESS_TOKENS_TABLE_NAME - access tokens table
+	ACCESS_TOKENS_TABLE_NAME = "user_access_tokens"
 	// USER_PERMISSIONS_TABLE_NAME - user permissions table
 	USER_PERMISSIONS_TABLE_NAME = "user_permissions"
 	// CERTS_TABLE_NAME - certificates table
@@ -73,6 +69,8 @@ const (
 	TAG_TABLE_NAME = "tags"
 	// PEER_ACK_TABLE - table for failover peer ack
 	PEER_ACK_TABLE = "peer_ack"
+	// SERVER_SETTINGS - table for server settings
+	SERVER_SETTINGS = "server_settings"
 	// == ERROR CONSTS ==
 	// NO_RECORD - no singular result found
 	NO_RECORD = "no result found"
@@ -129,6 +127,7 @@ var Tables = []string{
 	TAG_TABLE_NAME,
 	ACLS_TABLE_NAME,
 	PEER_ACK_TABLE,
+	SERVER_SETTINGS,
 }
 
 func getCurrentDB() map[string]interface{} {
@@ -160,7 +159,7 @@ func InitializeDatabase() error {
 		time.Sleep(2 * time.Second)
 	}
 	createTables()
-	return initializeUUID()
+	return nil
 }
 
 func createTables() {
@@ -173,35 +172,17 @@ func CreateTable(tableName string) error {
 	return getCurrentDB()[CREATE_TABLE].(func(string) error)(tableName)
 }
 
-// IsJSONString - checks if valid json
-func IsJSONString(value string) bool {
-	var jsonInt interface{}
-	var nodeInt models.Node
-	return json.Unmarshal([]byte(value), &jsonInt) == nil || json.Unmarshal([]byte(value), &nodeInt) == nil
-}
-
 // Insert - inserts object into db
 func Insert(key string, value string, tableName string) error {
 	dbMutex.Lock()
 	defer dbMutex.Unlock()
-	if key != "" && value != "" && IsJSONString(value) {
+	if key != "" && value != "" {
 		return getCurrentDB()[INSERT].(func(string, string, string) error)(key, value, tableName)
 	} else {
 		return errors.New("invalid insert " + key + " : " + value)
 	}
 }
 
-// InsertPeer - inserts peer into db
-func InsertPeer(key string, value string) error {
-	dbMutex.Lock()
-	defer dbMutex.Unlock()
-	if key != "" && value != "" && IsJSONString(value) {
-		return getCurrentDB()[INSERT_PEER].(func(string, string) error)(key, value)
-	} else {
-		return errors.New("invalid peer insert " + key + " : " + value)
-	}
-}
-
 // DeleteRecord - deletes a record from db
 func DeleteRecord(tableName string, key string) error {
 	dbMutex.Lock()
@@ -243,44 +224,6 @@ func FetchRecords(tableName string) (map[string]string, error) {
 	return getCurrentDB()[FETCH_ALL].(func(string) (map[string]string, error))(tableName)
 }
 
-// initializeUUID - create a UUID record for server if none exists
-func initializeUUID() error {
-	records, err := FetchRecords(SERVER_UUID_TABLE_NAME)
-	if err != nil {
-		if !IsEmptyRecord(err) {
-			return err
-		}
-	} else if len(records) > 0 {
-		return nil
-	}
-	// setup encryption keys
-	var trafficPubKey, trafficPrivKey, errT = box.GenerateKey(rand.Reader) // generate traffic keys
-	if errT != nil {
-		return errT
-	}
-	tPriv, err := ncutils.ConvertKeyToBytes(trafficPrivKey)
-	if err != nil {
-		return err
-	}
-
-	tPub, err := ncutils.ConvertKeyToBytes(trafficPubKey)
-	if err != nil {
-		return err
-	}
-
-	telemetry := models.Telemetry{
-		UUID:           uuid.NewString(),
-		TrafficKeyPriv: tPriv,
-		TrafficKeyPub:  tPub,
-	}
-	telJSON, err := json.Marshal(&telemetry)
-	if err != nil {
-		return err
-	}
-
-	return Insert(SERVER_UUID_RECORD_KEY, string(telJSON), SERVER_UUID_TABLE_NAME)
-}
-
 // CloseDB - closes a database gracefully
 func CloseDB() {
 	getCurrentDB()[CLOSE_DB].(func())()

+ 2 - 2
database/postgres.go

@@ -59,7 +59,7 @@ func pgCreateTable(tableName string) error {
 }
 
 func pgInsert(key string, value string, tableName string) error {
-	if key != "" && value != "" && IsJSONString(value) {
+	if key != "" && value != "" {
 		insertSQL := "INSERT INTO " + tableName + " (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = $3;"
 		statement, err := PGDB.Prepare(insertSQL)
 		if err != nil {
@@ -77,7 +77,7 @@ func pgInsert(key string, value string, tableName string) error {
 }
 
 func pgInsertPeer(key string, value string) error {
-	if key != "" && value != "" && IsJSONString(value) {
+	if key != "" && value != "" {
 		err := pgInsert(key, value, PEERS_TABLE_NAME)
 		if err != nil {
 			return err

+ 2 - 2
database/rqlite.go

@@ -43,7 +43,7 @@ func rqliteCreateTable(tableName string) error {
 }
 
 func rqliteInsert(key string, value string, tableName string) error {
-	if key != "" && value != "" && IsJSONString(value) {
+	if key != "" && value != "" {
 		_, err := RQliteDatabase.WriteOne("INSERT OR REPLACE INTO " + tableName + " (key, value) VALUES ('" + key + "', '" + value + "')")
 		if err != nil {
 			return err
@@ -54,7 +54,7 @@ func rqliteInsert(key string, value string, tableName string) error {
 }
 
 func rqliteInsertPeer(key string, value string) error {
-	if key != "" && value != "" && IsJSONString(value) {
+	if key != "" && value != "" {
 		_, err := RQliteDatabase.WriteOne("INSERT OR REPLACE INTO " + PEERS_TABLE_NAME + " (key, value) VALUES ('" + key + "', '" + value + "')")
 		if err != nil {
 			return err

+ 2 - 2
database/sqlite.go

@@ -61,7 +61,7 @@ func sqliteCreateTable(tableName string) error {
 }
 
 func sqliteInsert(key string, value string, tableName string) error {
-	if key != "" && value != "" && IsJSONString(value) {
+	if key != "" && value != "" {
 		insertSQL := "INSERT OR REPLACE INTO " + tableName + " (key, value) VALUES (?, ?)"
 		statement, err := SqliteDB.Prepare(insertSQL)
 		if err != nil {
@@ -78,7 +78,7 @@ func sqliteInsert(key string, value string, tableName string) error {
 }
 
 func sqliteInsertPeer(key string, value string) error {
-	if key != "" && value != "" && IsJSONString(value) {
+	if key != "" && value != "" {
 		err := sqliteInsert(key, value, PEERS_TABLE_NAME)
 		if err != nil {
 			return err

+ 0 - 59
database/statics.go

@@ -1,59 +0,0 @@
-package database
-
-import (
-	"encoding/json"
-	"strings"
-)
-
-// SetPeers - sets peers for a network
-func SetPeers(newPeers map[string]string, networkName string) bool {
-	areEqual := PeersAreEqual(newPeers, networkName)
-	if !areEqual {
-		jsonData, err := json.Marshal(newPeers)
-		if err != nil {
-			return false
-		}
-		InsertPeer(networkName, string(jsonData))
-		return true
-	}
-	return !areEqual
-}
-
-// GetPeers - gets peers for a given network
-func GetPeers(networkName string) (map[string]string, error) {
-	record, err := FetchRecord(PEERS_TABLE_NAME, networkName)
-	if err != nil && !IsEmptyRecord(err) {
-		return nil, err
-	}
-	currentDataMap := make(map[string]string)
-	if IsEmptyRecord(err) {
-		return currentDataMap, nil
-	}
-	err = json.Unmarshal([]byte(record), &currentDataMap)
-	return currentDataMap, err
-}
-
-// PeersAreEqual - checks if peers are the same
-func PeersAreEqual(toCompare map[string]string, networkName string) bool {
-	currentDataMap, err := GetPeers(networkName)
-	if err != nil {
-		return false
-	}
-	if len(currentDataMap) != len(toCompare) {
-		return false
-	}
-	for k := range currentDataMap {
-		if toCompare[k] != currentDataMap[k] {
-			return false
-		}
-	}
-	return true
-}
-
-// IsEmptyRecord - checks for if it's an empty record error or not
-func IsEmptyRecord(err error) bool {
-	if err == nil {
-		return false
-	}
-	return strings.Contains(err.Error(), NO_RECORD) || strings.Contains(err.Error(), NO_RECORDS)
-}

+ 11 - 0
database/utils.go

@@ -0,0 +1,11 @@
+package database
+
+import "strings"
+
+// IsEmptyRecord - checks for if it's an empty record error or not
+func IsEmptyRecord(err error) bool {
+	if err == nil {
+		return false
+	}
+	return strings.Contains(err.Error(), NO_RECORD) || strings.Contains(err.Error(), NO_RECORDS)
+}

+ 15 - 2
db/connector.go

@@ -2,7 +2,9 @@ package db
 
 import (
 	"errors"
-	"github.com/gravitl/netmaker/servercfg"
+	"os"
+
+	"github.com/gravitl/netmaker/config"
 	"gorm.io/gorm"
 )
 
@@ -14,10 +16,21 @@ type connector interface {
 	connect() (*gorm.DB, error)
 }
 
+// GetDB - gets the database type
+func GetDB() string {
+	database := "sqlite"
+	if os.Getenv("DATABASE") != "" {
+		database = os.Getenv("DATABASE")
+	} else if config.Config.Server.Database != "" {
+		database = config.Config.Server.Database
+	}
+	return database
+}
+
 // newConnector detects the database being
 // used and returns the corresponding connector.
 func newConnector() (connector, error) {
-	switch servercfg.GetDB() {
+	switch GetDB() {
 	case "sqlite":
 		return &sqliteConnector{}, nil
 	case "postgres":

+ 2 - 1
db/db.go

@@ -3,9 +3,10 @@ package db
 import (
 	"context"
 	"errors"
-	"gorm.io/gorm"
 	"net/http"
 	"time"
+
+	"gorm.io/gorm"
 )
 
 type ctxKey string

+ 70 - 2
db/postgres.go

@@ -2,7 +2,10 @@ package db
 
 import (
 	"fmt"
-	"github.com/gravitl/netmaker/servercfg"
+	"os"
+	"strconv"
+
+	"github.com/gravitl/netmaker/config"
 	"gorm.io/driver/postgres"
 	"gorm.io/gorm"
 	"gorm.io/gorm/logger"
@@ -15,7 +18,7 @@ type postgresConnector struct{}
 // postgresConnector.connect connects and
 // initializes a connection to postgres.
 func (pg *postgresConnector) connect() (*gorm.DB, error) {
-	pgConf := servercfg.GetSQLConf()
+	pgConf := GetSQLConf()
 	dsn := fmt.Sprintf(
 		"host=%s port=%d user=%s password=%s dbname=%s sslmode=%s connect_timeout=5",
 		pgConf.Host,
@@ -47,3 +50,68 @@ func (pg *postgresConnector) connect() (*gorm.DB, error) {
 
 	return db, nil
 }
+func GetSQLConf() config.SQLConfig {
+	var cfg config.SQLConfig
+	cfg.Host = GetSQLHost()
+	cfg.Port = GetSQLPort()
+	cfg.Username = GetSQLUser()
+	cfg.Password = GetSQLPass()
+	cfg.DB = GetSQLDB()
+	cfg.SSLMode = GetSQLSSLMode()
+	return cfg
+}
+func GetSQLHost() string {
+	host := "localhost"
+	if os.Getenv("SQL_HOST") != "" {
+		host = os.Getenv("SQL_HOST")
+	} else if config.Config.SQL.Host != "" {
+		host = config.Config.SQL.Host
+	}
+	return host
+}
+func GetSQLPort() int32 {
+	port := int32(5432)
+	envport, err := strconv.Atoi(os.Getenv("SQL_PORT"))
+	if err == nil && envport != 0 {
+		port = int32(envport)
+	} else if config.Config.SQL.Port != 0 {
+		port = config.Config.SQL.Port
+	}
+	return port
+}
+func GetSQLUser() string {
+	user := "postgres"
+	if os.Getenv("SQL_USER") != "" {
+		user = os.Getenv("SQL_USER")
+	} else if config.Config.SQL.Username != "" {
+		user = config.Config.SQL.Username
+	}
+	return user
+}
+func GetSQLPass() string {
+	pass := "nopass"
+	if os.Getenv("SQL_PASS") != "" {
+		pass = os.Getenv("SQL_PASS")
+	} else if config.Config.SQL.Password != "" {
+		pass = config.Config.SQL.Password
+	}
+	return pass
+}
+func GetSQLDB() string {
+	db := "netmaker"
+	if os.Getenv("SQL_DB") != "" {
+		db = os.Getenv("SQL_DB")
+	} else if config.Config.SQL.DB != "" {
+		db = config.Config.SQL.DB
+	}
+	return db
+}
+func GetSQLSSLMode() string {
+	sslmode := "disable"
+	if os.Getenv("SQL_SSL_MODE") != "" {
+		sslmode = os.Getenv("SQL_SSL_MODE")
+	} else if config.Config.SQL.SSLMode != "" {
+		sslmode = config.Config.SQL.SSLMode
+	}
+	return sslmode
+}

+ 10 - 5
logic/auth.go

@@ -1,12 +1,16 @@
 package logic
 
 import (
+	"context"
 	"encoding/base64"
 	"encoding/json"
 	"errors"
 	"fmt"
 	"time"
 
+	"github.com/gravitl/netmaker/db"
+	"github.com/gravitl/netmaker/schema"
+
 	"github.com/go-playground/validator/v10"
 	"golang.org/x/crypto/bcrypt"
 	"golang.org/x/exp/slog"
@@ -28,6 +32,8 @@ func ClearSuperUserCache() {
 	superUser = models.User{}
 }
 
+var InitializeAuthProvider = func() string { return "" }
+
 // HasSuperAdmin - checks if server has an superadmin/owner
 func HasSuperAdmin() (bool, error) {
 
@@ -349,19 +355,18 @@ func ValidateUser(user *models.User) error {
 }
 
 // DeleteUser - deletes a given user
-func DeleteUser(user string) (bool, error) {
+func DeleteUser(user string) error {
 
 	if userRecord, err := database.FetchRecord(database.USERS_TABLE_NAME, user); err != nil || len(userRecord) == 0 {
-		return false, errors.New("user does not exist")
+		return errors.New("user does not exist")
 	}
 
 	err := database.DeleteRecord(database.USERS_TABLE_NAME, user)
 	if err != nil {
-		return false, err
+		return err
 	}
 	go RemoveUserFromAclPolicy(user)
-
-	return true, nil
+	return (&schema.UserAccessToken{UserName: user}).DeleteAllUserTokens(db.WithContext(context.TODO()))
 }
 
 func SetAuthSecret(secret string) error {

+ 1 - 2
logic/dns.go

@@ -12,7 +12,6 @@ import (
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/models"
-	"github.com/gravitl/netmaker/servercfg"
 	"github.com/txn2/txeh"
 )
 
@@ -106,7 +105,7 @@ func GetNodeDNS(network string) ([]models.DNSEntry, error) {
 	if err != nil {
 		return dns, err
 	}
-	defaultDomain := servercfg.GetDefaultDomain()
+	defaultDomain := GetDefaultDomain()
 	for _, node := range nodes {
 		if node.Network != network {
 			continue

+ 2 - 1
logic/enrollmentkey.go

@@ -38,7 +38,7 @@ var (
 )
 
 // CreateEnrollmentKey - creates a new enrollment key in db
-func CreateEnrollmentKey(uses int, expiration time.Time, networks, tags []string, groups []models.TagID, unlimited bool, relay uuid.UUID, defaultKey bool) (*models.EnrollmentKey, error) {
+func CreateEnrollmentKey(uses int, expiration time.Time, networks, tags []string, groups []models.TagID, unlimited bool, relay uuid.UUID, defaultKey, autoEgress bool) (*models.EnrollmentKey, error) {
 	newKeyID, err := getUniqueEnrollmentID()
 	if err != nil {
 		return nil, err
@@ -54,6 +54,7 @@ func CreateEnrollmentKey(uses int, expiration time.Time, networks, tags []string
 		Relay:         relay,
 		Groups:        groups,
 		Default:       defaultKey,
+		AutoEgress:    autoEgress,
 	}
 	if uses > 0 {
 		k.UsesRemaining = uses

+ 13 - 13
logic/enrollmentkey_test.go

@@ -14,35 +14,35 @@ func TestCreateEnrollmentKey(t *testing.T) {
 	database.InitializeDatabase()
 	defer database.CloseDB()
 	t.Run("Can_Not_Create_Key", func(t *testing.T) {
-		newKey, err := CreateEnrollmentKey(0, time.Time{}, nil, nil, nil, false, uuid.Nil, false)
+		newKey, err := CreateEnrollmentKey(0, time.Time{}, nil, nil, nil, false, uuid.Nil, false, false)
 		assert.Nil(t, newKey)
 		assert.NotNil(t, err)
 		assert.ErrorIs(t, err, models.ErrInvalidEnrollmentKey)
 	})
 	t.Run("Can_Create_Key_Uses", func(t *testing.T) {
-		newKey, err := CreateEnrollmentKey(1, time.Time{}, nil, nil, nil, false, uuid.Nil, false)
+		newKey, err := CreateEnrollmentKey(1, time.Time{}, nil, nil, nil, false, uuid.Nil, false, false)
 		assert.Nil(t, err)
 		assert.Equal(t, 1, newKey.UsesRemaining)
 		assert.True(t, newKey.IsValid())
 	})
 	t.Run("Can_Create_Key_Time", func(t *testing.T) {
-		newKey, err := CreateEnrollmentKey(0, time.Now().Add(time.Minute), nil, nil, nil, false, uuid.Nil, false)
+		newKey, err := CreateEnrollmentKey(0, time.Now().Add(time.Minute), nil, nil, nil, false, uuid.Nil, false, false)
 		assert.Nil(t, err)
 		assert.True(t, newKey.IsValid())
 	})
 	t.Run("Can_Create_Key_Unlimited", func(t *testing.T) {
-		newKey, err := CreateEnrollmentKey(0, time.Time{}, nil, nil, nil, true, uuid.Nil, false)
+		newKey, err := CreateEnrollmentKey(0, time.Time{}, nil, nil, nil, true, uuid.Nil, false, false)
 		assert.Nil(t, err)
 		assert.True(t, newKey.IsValid())
 	})
 	t.Run("Can_Create_Key_WithNetworks", func(t *testing.T) {
-		newKey, err := CreateEnrollmentKey(0, time.Time{}, []string{"mynet", "skynet"}, nil, nil, true, uuid.Nil, false)
+		newKey, err := CreateEnrollmentKey(0, time.Time{}, []string{"mynet", "skynet"}, nil, nil, true, uuid.Nil, false, false)
 		assert.Nil(t, err)
 		assert.True(t, newKey.IsValid())
 		assert.True(t, len(newKey.Networks) == 2)
 	})
 	t.Run("Can_Create_Key_WithTags", func(t *testing.T) {
-		newKey, err := CreateEnrollmentKey(0, time.Time{}, nil, []string{"tag1", "tag2"}, nil, true, uuid.Nil, false)
+		newKey, err := CreateEnrollmentKey(0, time.Time{}, nil, []string{"tag1", "tag2"}, nil, true, uuid.Nil, false, false)
 		assert.Nil(t, err)
 		assert.True(t, newKey.IsValid())
 		assert.True(t, len(newKey.Tags) == 2)
@@ -62,7 +62,7 @@ func TestCreateEnrollmentKey(t *testing.T) {
 func TestDelete_EnrollmentKey(t *testing.T) {
 	database.InitializeDatabase()
 	defer database.CloseDB()
-	newKey, _ := CreateEnrollmentKey(0, time.Time{}, []string{"mynet", "skynet"}, nil, nil, true, uuid.Nil, false)
+	newKey, _ := CreateEnrollmentKey(0, time.Time{}, []string{"mynet", "skynet"}, nil, nil, true, uuid.Nil, false, false)
 	t.Run("Can_Delete_Key", func(t *testing.T) {
 		assert.True(t, newKey.IsValid())
 		err := DeleteEnrollmentKey(newKey.Value, false)
@@ -83,7 +83,7 @@ func TestDelete_EnrollmentKey(t *testing.T) {
 func TestDecrement_EnrollmentKey(t *testing.T) {
 	database.InitializeDatabase()
 	defer database.CloseDB()
-	newKey, _ := CreateEnrollmentKey(1, time.Time{}, nil, nil, nil, false, uuid.Nil, false)
+	newKey, _ := CreateEnrollmentKey(1, time.Time{}, nil, nil, nil, false, uuid.Nil, false, false)
 	t.Run("Check_initial_uses", func(t *testing.T) {
 		assert.True(t, newKey.IsValid())
 		assert.Equal(t, newKey.UsesRemaining, 1)
@@ -107,9 +107,9 @@ func TestDecrement_EnrollmentKey(t *testing.T) {
 func TestUsability_EnrollmentKey(t *testing.T) {
 	database.InitializeDatabase()
 	defer database.CloseDB()
-	key1, _ := CreateEnrollmentKey(1, time.Time{}, nil, nil, nil, false, uuid.Nil, false)
-	key2, _ := CreateEnrollmentKey(0, time.Now().Add(time.Minute<<4), nil, nil, nil, false, uuid.Nil, false)
-	key3, _ := CreateEnrollmentKey(0, time.Time{}, nil, nil, nil, true, uuid.Nil, false)
+	key1, _ := CreateEnrollmentKey(1, time.Time{}, nil, nil, nil, false, uuid.Nil, false, false)
+	key2, _ := CreateEnrollmentKey(0, time.Now().Add(time.Minute<<4), nil, nil, nil, false, uuid.Nil, false, false)
+	key3, _ := CreateEnrollmentKey(0, time.Time{}, nil, nil, nil, true, uuid.Nil, false, false)
 	t.Run("Check if valid use key can be used", func(t *testing.T) {
 		assert.Equal(t, key1.UsesRemaining, 1)
 		ok := TryToUseEnrollmentKey(key1)
@@ -145,7 +145,7 @@ func removeAllEnrollments() {
 func TestTokenize_EnrollmentKeys(t *testing.T) {
 	database.InitializeDatabase()
 	defer database.CloseDB()
-	newKey, _ := CreateEnrollmentKey(0, time.Time{}, []string{"mynet", "skynet"}, nil, nil, true, uuid.Nil, false)
+	newKey, _ := CreateEnrollmentKey(0, time.Time{}, []string{"mynet", "skynet"}, nil, nil, true, uuid.Nil, false, false)
 	const defaultValue = "MwE5MwE5MwE5MwE5MwE5MwE5MwE5MwE5"
 	const b64value = "eyJzZXJ2ZXIiOiJhcGkubXlzZXJ2ZXIuY29tIiwidmFsdWUiOiJNd0U1TXdFNU13RTVNd0U1TXdFNU13RTVNd0U1TXdFNSJ9"
 	const serverAddr = "api.myserver.com"
@@ -178,7 +178,7 @@ func TestTokenize_EnrollmentKeys(t *testing.T) {
 func TestDeTokenize_EnrollmentKeys(t *testing.T) {
 	database.InitializeDatabase()
 	defer database.CloseDB()
-	newKey, _ := CreateEnrollmentKey(0, time.Time{}, []string{"mynet", "skynet"}, nil, nil, true, uuid.Nil, false)
+	newKey, _ := CreateEnrollmentKey(0, time.Time{}, []string{"mynet", "skynet"}, nil, nil, true, uuid.Nil, false, false)
 	const b64Value = "eyJzZXJ2ZXIiOiJhcGkubXlzZXJ2ZXIuY29tIiwidmFsdWUiOiJNd0U1TXdFNU13RTVNd0U1TXdFNU13RTVNd0U1TXdFNSJ9"
 	const serverAddr = "api.myserver.com"
 

+ 1 - 1
logic/hosts.go

@@ -228,7 +228,7 @@ func CreateHost(h *models.Host) error {
 		return err
 	}
 	h.HostPass = string(hash)
-	h.AutoUpdate = servercfg.AutoUpdateEnabled()
+	h.AutoUpdate = AutoUpdateEnabled()
 	checkForZombieHosts(h)
 	return UpsertHost(h)
 }

+ 59 - 16
logic/jwts.go

@@ -1,8 +1,11 @@
 package logic
 
 import (
+	"context"
 	"errors"
 	"fmt"
+	"github.com/gravitl/netmaker/db"
+	"github.com/gravitl/netmaker/schema"
 	"strings"
 	"time"
 
@@ -53,17 +56,19 @@ func CreateJWT(uuid string, macAddress string, network string) (response string,
 }
 
 // CreateUserJWT - creates a user jwt token
-func CreateUserJWT(username string, role models.UserRoleID) (response string, err error) {
-	expirationTime := time.Now().Add(servercfg.GetServerConfig().JwtValidityDuration)
+func CreateUserAccessJwtToken(username string, role models.UserRoleID, d time.Time, tokenID string) (response string, err error) {
 	claims := &models.UserClaims{
 		UserName:       username,
 		Role:           role,
-		RacAutoDisable: servercfg.GetRacAutoDisable() && (role != models.SuperAdminRole && role != models.AdminRole),
+		TokenType:      models.AccessTokenType,
+		Api:            servercfg.GetAPIHost(),
+		RacAutoDisable: GetRacAutoDisable() && (role != models.SuperAdminRole && role != models.AdminRole),
 		RegisteredClaims: jwt.RegisteredClaims{
 			Issuer:    "Netmaker",
 			Subject:   fmt.Sprintf("user|%s", username),
 			IssuedAt:  jwt.NewNumericDate(time.Now()),
-			ExpiresAt: jwt.NewNumericDate(expirationTime),
+			ExpiresAt: jwt.NewNumericDate(d),
+			ID:        tokenID,
 		},
 	}
 
@@ -75,16 +80,29 @@ func CreateUserJWT(username string, role models.UserRoleID) (response string, er
 	return "", err
 }
 
-// VerifyJWT verifies Auth Header
-func VerifyJWT(bearerToken string) (username string, issuperadmin, isadmin bool, err error) {
-	token := ""
-	tokenSplit := strings.Split(bearerToken, " ")
-	if len(tokenSplit) > 1 {
-		token = tokenSplit[1]
-	} else {
-		return "", false, false, errors.New("invalid auth header")
+// CreateUserJWT - creates a user jwt token
+func CreateUserJWT(username string, role models.UserRoleID) (response string, err error) {
+	settings := GetServerSettings()
+	expirationTime := time.Now().Add(time.Duration(settings.JwtValidityDuration) * time.Second)
+	claims := &models.UserClaims{
+		UserName:       username,
+		Role:           role,
+		TokenType:      models.UserIDTokenType,
+		RacAutoDisable: settings.RacAutoDisable && (role != models.SuperAdminRole && role != models.AdminRole),
+		RegisteredClaims: jwt.RegisteredClaims{
+			Issuer:    "Netmaker",
+			Subject:   fmt.Sprintf("user|%s", username),
+			IssuedAt:  jwt.NewNumericDate(time.Now()),
+			ExpiresAt: jwt.NewNumericDate(expirationTime),
+		},
 	}
-	return VerifyUserToken(token)
+
+	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+	tokenString, err := token.SignedString(jwtSecretKey)
+	if err == nil {
+		return tokenString, nil
+	}
+	return "", err
 }
 
 func GetUserNameFromToken(authtoken string) (username string, err error) {
@@ -107,6 +125,20 @@ func GetUserNameFromToken(authtoken string) (username string, err error) {
 	if err != nil {
 		return "", Unauthorized_Err
 	}
+	if claims.TokenType == models.AccessTokenType {
+		jti := claims.ID
+		if jti != "" {
+			a := schema.UserAccessToken{ID: jti}
+			// check if access token is active
+			err := a.Get(db.WithContext(context.TODO()))
+			if err != nil {
+				err = errors.New("token revoked")
+				return "", err
+			}
+			a.LastUsed = time.Now()
+			a.Update(db.WithContext(context.TODO()))
+		}
+	}
 
 	if token != nil && token.Valid {
 		var user *models.User
@@ -131,15 +163,26 @@ func GetUserNameFromToken(authtoken string) (username string, err error) {
 // VerifyUserToken func will used to Verify the JWT Token while using APIS
 func VerifyUserToken(tokenString string) (username string, issuperadmin, isadmin bool, err error) {
 	claims := &models.UserClaims{}
-
 	if tokenString == servercfg.GetMasterKey() && servercfg.GetMasterKey() != "" {
 		return MasterUser, true, true, nil
 	}
-
 	token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
 		return jwtSecretKey, nil
 	})
-
+	if claims.TokenType == models.AccessTokenType {
+		jti := claims.ID
+		if jti != "" {
+			a := schema.UserAccessToken{ID: jti}
+			// check if access token is active
+			err := a.Get(db.WithContext(context.TODO()))
+			if err != nil {
+				err = errors.New("token revoked")
+				return "", false, false, err
+			}
+			a.LastUsed = time.Now()
+			a.Update(db.WithContext(context.TODO()))
+		}
+	}
 	if token != nil && token.Valid {
 		var user *models.User
 		// check that user exists

+ 1 - 0
logic/networks.go

@@ -302,6 +302,7 @@ func CreateNetwork(network models.Network) (models.Network, error) {
 		true,
 		uuid.Nil,
 		true,
+		false,
 	)
 
 	return network, nil

+ 1 - 1
logic/peers.go

@@ -157,7 +157,7 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 		Peers:           []wgtypes.PeerConfig{},
 		NodePeers:       []wgtypes.PeerConfig{},
 		HostNetworkInfo: models.HostInfoMap{},
-		ServerConfig:    servercfg.ServerInfo,
+		ServerConfig:    GetServerInfo(),
 	}
 	defer func() {
 		if !hostPeerUpdate.FwUpdate.AllowAll {

+ 330 - 0
logic/settings.go

@@ -0,0 +1,330 @@
+package logic
+
+import (
+	"encoding/json"
+	"os"
+	"regexp"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/gravitl/netmaker/config"
+	"github.com/gravitl/netmaker/database"
+	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/servercfg"
+)
+
+var serverSettingsDBKey = "server_cfg"
+
+func GetServerSettings() (s models.ServerSettings) {
+	data, err := database.FetchRecord(database.SERVER_SETTINGS, serverSettingsDBKey)
+	if err != nil {
+		return
+	}
+	json.Unmarshal([]byte(data), &s)
+	return
+}
+
+func UpsertServerSettings(s models.ServerSettings) error {
+	data, err := json.Marshal(s)
+	if err != nil {
+		return err
+	}
+	err = database.Insert(serverSettingsDBKey, string(data), database.SERVER_SETTINGS)
+	if err != nil {
+		return err
+	}
+	go reInit()
+	return nil
+}
+
+func ValidateNewSettings(req models.ServerSettings) bool {
+	// TODO: add checks for different fields
+	return true
+}
+
+func reInit() {
+	InitializeAuthProvider()
+	EmailInit()
+}
+
+func GetServerSettingsFromEnv() (s models.ServerSettings) {
+
+	s = models.ServerSettings{
+		NetclientAutoUpdate:        servercfg.AutoUpdateEnabled(),
+		Verbosity:                  servercfg.GetVerbosity(),
+		AuthProvider:               os.Getenv("AUTH_PROVIDER"),
+		OIDCIssuer:                 os.Getenv("OIDC_ISSUER"),
+		ClientID:                   os.Getenv("CLIENT_ID"),
+		ClientSecret:               os.Getenv("CLIENT_SECRET"),
+		AzureTenant:                servercfg.GetAzureTenant(),
+		Telemetry:                  servercfg.Telemetry(),
+		BasicAuth:                  servercfg.IsBasicAuthEnabled(),
+		JwtValidityDuration:        servercfg.GetJwtValidityDurationFromEnv(),
+		RacAutoDisable:             servercfg.GetRacAutoDisable(),
+		RacRestrictToSingleNetwork: servercfg.GetRacRestrictToSingleNetwork(),
+		EndpointDetection:          servercfg.IsEndpointDetectionEnabled(),
+		AllowedEmailDomains:        servercfg.GetAllowedEmailDomains(),
+		EmailSenderAddr:            servercfg.GetSenderEmail(),
+		EmailSenderUser:            servercfg.GetSenderUser(),
+		EmailSenderPassword:        servercfg.GetEmaiSenderPassword(),
+		SmtpHost:                   servercfg.GetSmtpHost(),
+		SmtpPort:                   servercfg.GetSmtpPort(),
+		MetricInterval:             servercfg.GetMetricInterval(),
+		MetricsPort:                servercfg.GetMetricsPort(),
+		ManageDNS:                  servercfg.GetManageDNS(),
+		DefaultDomain:              servercfg.GetDefaultDomain(),
+		Stun:                       servercfg.IsStunEnabled(),
+		StunServers:                servercfg.GetStunServers(),
+		TextSize:                   "16",
+		Theme:                      models.Dark,
+		ReducedMotion:              false,
+	}
+
+	return
+}
+
+// GetServerConfig - gets the server config into memory from file or env
+func GetServerConfig() config.ServerConfig {
+	var cfg config.ServerConfig
+	settings := GetServerSettings()
+	cfg.APIConnString = servercfg.GetAPIConnString()
+	cfg.CoreDNSAddr = servercfg.GetCoreDNSAddr()
+	cfg.APIHost = servercfg.GetAPIHost()
+	cfg.APIPort = servercfg.GetAPIPort()
+	cfg.MasterKey = "(hidden)"
+	cfg.DNSKey = "(hidden)"
+	cfg.AllowedOrigin = servercfg.GetAllowedOrigin()
+	cfg.RestBackend = "off"
+	cfg.NodeID = servercfg.GetNodeID()
+	cfg.BrokerType = servercfg.GetBrokerType()
+	cfg.EmqxRestEndpoint = servercfg.GetEmqxRestEndpoint()
+	if settings.NetclientAutoUpdate {
+		cfg.NetclientAutoUpdate = "enabled"
+	} else {
+		cfg.NetclientAutoUpdate = "disabled"
+	}
+	if servercfg.IsRestBackend() {
+		cfg.RestBackend = "on"
+	}
+	cfg.DNSMode = "off"
+	if servercfg.IsDNSMode() {
+		cfg.DNSMode = "on"
+	}
+	cfg.DisplayKeys = "off"
+	if servercfg.IsDisplayKeys() {
+		cfg.DisplayKeys = "on"
+	}
+	cfg.DisableRemoteIPCheck = "off"
+	if servercfg.DisableRemoteIPCheck() {
+		cfg.DisableRemoteIPCheck = "on"
+	}
+	cfg.Database = servercfg.GetDB()
+	cfg.Platform = servercfg.GetPlatform()
+	cfg.Version = servercfg.GetVersion()
+	cfg.PublicIp = servercfg.GetServerHostIP()
+
+	// == auth config ==
+	var authInfo = GetAuthProviderInfo(settings)
+	cfg.AuthProvider = authInfo[0]
+	cfg.ClientID = authInfo[1]
+	cfg.ClientSecret = authInfo[2]
+	cfg.FrontendURL = servercfg.GetFrontendURL()
+	cfg.AzureTenant = settings.AzureTenant
+	cfg.Telemetry = settings.Telemetry
+	cfg.Server = servercfg.GetServer()
+	cfg.Verbosity = settings.Verbosity
+	cfg.IsPro = "no"
+	if servercfg.IsPro {
+		cfg.IsPro = "yes"
+	}
+	cfg.JwtValidityDuration = time.Duration(settings.JwtValidityDuration) * time.Second
+	cfg.RacAutoDisable = settings.RacAutoDisable
+	cfg.RacRestrictToSingleNetwork = settings.RacRestrictToSingleNetwork
+	cfg.MetricInterval = settings.MetricInterval
+	cfg.ManageDNS = settings.ManageDNS
+	cfg.Stun = settings.Stun
+	cfg.StunServers = settings.StunServers
+	cfg.DefaultDomain = settings.DefaultDomain
+	return cfg
+}
+
+// GetServerInfo - gets the server config into memory from file or env
+func GetServerInfo() models.ServerConfig {
+	var cfg models.ServerConfig
+	serverSettings := GetServerSettings()
+	cfg.Server = servercfg.GetServer()
+	if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
+		cfg.MQUserName = "HOST_ID"
+		cfg.MQPassword = "HOST_PASS"
+	} else {
+		cfg.MQUserName = servercfg.GetMqUserName()
+		cfg.MQPassword = servercfg.GetMqPassword()
+	}
+	cfg.API = servercfg.GetAPIConnString()
+	cfg.CoreDNSAddr = servercfg.GetCoreDNSAddr()
+	cfg.APIPort = servercfg.GetAPIPort()
+	cfg.DNSMode = "off"
+	cfg.Broker = servercfg.GetPublicBrokerEndpoint()
+	cfg.BrokerType = servercfg.GetBrokerType()
+	if servercfg.IsDNSMode() {
+		cfg.DNSMode = "on"
+	}
+	cfg.Version = servercfg.GetVersion()
+	cfg.IsPro = servercfg.IsPro
+	cfg.MetricInterval = serverSettings.MetricInterval
+	cfg.MetricsPort = serverSettings.MetricsPort
+	cfg.ManageDNS = serverSettings.ManageDNS
+	cfg.Stun = serverSettings.Stun
+	cfg.StunServers = serverSettings.StunServers
+	cfg.DefaultDomain = serverSettings.DefaultDomain
+	cfg.EndpointDetection = serverSettings.EndpointDetection
+	return cfg
+}
+
+// GetDefaultDomain - get the default domain
+func GetDefaultDomain() string {
+	return GetServerSettings().DefaultDomain
+}
+
+func ValidateDomain(domain string) bool {
+	domainPattern := `[a-zA-Z0-9][a-zA-Z0-9_-]{0,62}(\.[a-zA-Z0-9][a-zA-Z0-9_-]{0,62})*(\.[a-zA-Z][a-zA-Z0-9]{0,10}){1}`
+
+	exp := regexp.MustCompile("^" + domainPattern + "$")
+
+	return exp.MatchString(domain)
+}
+
+// Telemetry - checks if telemetry data should be sent
+func Telemetry() string {
+	return GetServerSettings().Telemetry
+}
+
+// GetJwtValidityDuration - returns the JWT validity duration in seconds
+func GetJwtValidityDuration() time.Duration {
+	return GetServerConfig().JwtValidityDuration
+}
+
+// GetRacAutoDisable - returns whether the feature to autodisable RAC is enabled
+func GetRacAutoDisable() bool {
+	return GetServerSettings().RacAutoDisable
+}
+
+// GetRacRestrictToSingleNetwork - returns whether the feature to allow simultaneous network connections via RAC is enabled
+func GetRacRestrictToSingleNetwork() bool {
+	return GetServerSettings().RacRestrictToSingleNetwork
+}
+
+func GetSmtpHost() string {
+	return GetServerSettings().SmtpHost
+}
+
+func GetSmtpPort() int {
+	return GetServerSettings().SmtpPort
+}
+
+func GetSenderEmail() string {
+	return GetServerSettings().EmailSenderAddr
+}
+
+func GetSenderUser() string {
+	return GetServerSettings().EmailSenderUser
+}
+
+func GetEmaiSenderPassword() string {
+	return GetServerSettings().EmailSenderPassword
+}
+
+// AutoUpdateEnabled returns a boolean indicating whether netclient auto update is enabled or disabled
+// default is enabled
+func AutoUpdateEnabled() bool {
+	return GetServerSettings().NetclientAutoUpdate
+}
+
+// GetAuthProviderInfo = gets the oauth provider info
+func GetAuthProviderInfo(settings models.ServerSettings) (pi []string) {
+	var authProvider = ""
+
+	defer func() {
+		if authProvider == "oidc" {
+			if settings.OIDCIssuer != "" {
+				pi = append(pi, settings.OIDCIssuer)
+			} else {
+				pi = []string{"", "", ""}
+			}
+		}
+	}()
+
+	if settings.AuthProvider != "" && settings.ClientID != "" && settings.ClientSecret != "" {
+		authProvider = strings.ToLower(settings.AuthProvider)
+		if authProvider == "google" || authProvider == "azure-ad" || authProvider == "github" || authProvider == "oidc" {
+			return []string{authProvider, settings.ClientID, settings.ClientSecret}
+		} else {
+			authProvider = ""
+		}
+	}
+	return []string{"", "", ""}
+}
+
+// GetAzureTenant - retrieve the azure tenant ID from env variable or config file
+func GetAzureTenant() string {
+	return GetServerSettings().AzureTenant
+}
+
+// GetMetricsPort - get metrics port
+func GetMetricsPort() int {
+	return GetServerSettings().MetricsPort
+}
+
+// GetMetricInterval - get the publish metric interval
+func GetMetricIntervalInMinutes() time.Duration {
+	//default 15 minutes
+	mi := "15"
+	if os.Getenv("PUBLISH_METRIC_INTERVAL") != "" {
+		mi = os.Getenv("PUBLISH_METRIC_INTERVAL")
+	}
+	interval, err := strconv.Atoi(mi)
+	if err != nil {
+		interval = 15
+	}
+
+	return time.Duration(interval) * time.Minute
+}
+
+// GetMetricInterval - get the publish metric interval
+func GetMetricInterval() string {
+	return GetServerSettings().MetricInterval
+}
+
+// GetManageDNS - if manage DNS enabled or not
+func GetManageDNS() bool {
+	return GetServerSettings().ManageDNS
+}
+
+// IsBasicAuthEnabled - checks if basic auth has been configured to be turned off
+func IsBasicAuthEnabled() bool {
+	return GetServerSettings().BasicAuth
+}
+
+// IsEndpointDetectionEnabled - returns true if endpoint detection enabled
+func IsEndpointDetectionEnabled() bool {
+	return GetServerSettings().EndpointDetection
+}
+
+// IsStunEnabled - returns true if STUN set to on
+func IsStunEnabled() bool {
+	return GetServerSettings().Stun
+}
+
+func GetStunServers() string {
+	return GetServerSettings().StunServers
+}
+
+// GetAllowedEmailDomains - gets the allowed email domains for oauth signup
+func GetAllowedEmailDomains() string {
+	return GetServerSettings().AllowedEmailDomains
+}
+
+func GetVerbosity() int32 {
+	return GetServerSettings().Verbosity
+}

+ 2 - 1
logic/telemetry.go

@@ -7,6 +7,7 @@ import (
 
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
+
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/posthog/posthog-go"
@@ -33,7 +34,7 @@ func SetFreeTierForTelemetry(freeTierFlag bool) {
 
 // sendTelemetry - gathers telemetry data and sends to posthog
 func sendTelemetry() error {
-	if servercfg.Telemetry() == "off" {
+	if Telemetry() == "off" {
 		return nil
 	}
 

+ 1 - 0
logic/user_mgmt.go

@@ -62,6 +62,7 @@ var CreateDefaultUserPolicies = func(netID models.NetworkID) {}
 var GetUserGroupsInNetwork = func(netID models.NetworkID) (networkGrps map[models.UserGroupID]models.UserGroup) { return }
 var GetUserGroup = func(groupId models.UserGroupID) (userGrps models.UserGroup, err error) { return }
 var AddGlobalNetRolesToAdmins = func(u models.User) {}
+var EmailInit = func() {}
 
 // GetRole - fetches role template by id
 func GetRole(roleID models.UserRoleID) (models.UserRolePermissionTemplate, error) {

+ 50 - 1
main.go

@@ -3,6 +3,8 @@ package main
 
 import (
 	"context"
+	"crypto/rand"
+	"encoding/json"
 	"flag"
 	"fmt"
 	"os"
@@ -12,9 +14,11 @@ import (
 	"sync"
 	"syscall"
 
+	"github.com/google/uuid"
 	"github.com/gravitl/netmaker/config"
 	controller "github.com/gravitl/netmaker/controllers"
 	"github.com/gravitl/netmaker/database"
+	"github.com/gravitl/netmaker/db"
 	"github.com/gravitl/netmaker/functions"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
@@ -22,9 +26,11 @@ import (
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/mq"
 	"github.com/gravitl/netmaker/netclient/ncutils"
+	"github.com/gravitl/netmaker/schema"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/serverctl"
 	_ "go.uber.org/automaxprocs"
+	"golang.org/x/crypto/nacl/box"
 	"golang.org/x/exp/slog"
 )
 
@@ -99,8 +105,13 @@ func initialize() { // Client Mode Prereq Check
 	if err = database.InitializeDatabase(); err != nil {
 		logger.FatalLog("Error connecting to database: ", err.Error())
 	}
+	// initialize sql schema db.
+	err = db.InitializeDB(schema.ListModels()...)
+	if err != nil {
+		logger.FatalLog("Error connecting to v1 database: ", err.Error())
+	}
 	logger.Log(0, "database successfully connected")
-
+	initializeUUID()
 	//initialize cache
 	_, _ = logic.GetNetworks()
 	_, _ = logic.GetAllNodes()
@@ -247,3 +258,41 @@ func setGarbageCollection() {
 		debug.SetGCPercent(ncutils.DEFAULT_GC_PERCENT)
 	}
 }
+
+// initializeUUID - create a UUID record for server if none exists
+func initializeUUID() error {
+	records, err := database.FetchRecords(database.SERVER_UUID_TABLE_NAME)
+	if err != nil {
+		if !database.IsEmptyRecord(err) {
+			return err
+		}
+	} else if len(records) > 0 {
+		return nil
+	}
+	// setup encryption keys
+	var trafficPubKey, trafficPrivKey, errT = box.GenerateKey(rand.Reader) // generate traffic keys
+	if errT != nil {
+		return errT
+	}
+	tPriv, err := ncutils.ConvertKeyToBytes(trafficPrivKey)
+	if err != nil {
+		return err
+	}
+
+	tPub, err := ncutils.ConvertKeyToBytes(trafficPubKey)
+	if err != nil {
+		return err
+	}
+
+	telemetry := models.Telemetry{
+		UUID:           uuid.NewString(),
+		TrafficKeyPriv: tPriv,
+		TrafficKeyPub:  tPub,
+	}
+	telJSON, err := json.Marshal(&telemetry)
+	if err != nil {
+		return err
+	}
+
+	return database.Insert(database.SERVER_UUID_RECORD_KEY, string(telJSON), database.SERVER_UUID_TABLE_NAME)
+}

+ 9 - 0
migrate/migrate.go

@@ -20,6 +20,7 @@ import (
 
 // Run - runs all migrations
 func Run() {
+	settings()
 	updateEnrollmentKeys()
 	assignSuperAdmin()
 	createDefaultTagsAndPolicies()
@@ -151,6 +152,7 @@ func updateEnrollmentKeys() {
 			true,
 			uuid.Nil,
 			true,
+			false,
 		)
 
 	}
@@ -496,3 +498,10 @@ func migrateToGws() {
 		logic.DeleteTag(models.TagID(fmt.Sprintf("%s.%s", netI.NetID, models.OldRemoteAccessTagName)), true)
 	}
 }
+
+func settings() {
+	_, err := database.FetchRecords(database.SERVER_SETTINGS)
+	if database.IsEmptyRecord(err) {
+		logic.UpsertServerSettings(logic.GetServerSettingsFromEnv())
+	}
+}

+ 0 - 13
models/accessToken.go

@@ -1,13 +0,0 @@
-package models
-
-// AccessToken - token used to access netmaker
-type AccessToken struct {
-	APIConnString string `json:"apiconnstring"`
-	ClientConfig
-}
-
-// ClientConfig - the config of the client
-type ClientConfig struct {
-	Network string `json:"network"`
-	Key     string `json:"key"`
-}

+ 2 - 0
models/enrollment_key.go

@@ -54,6 +54,7 @@ type EnrollmentKey struct {
 	Relay         uuid.UUID `json:"relay"`
 	Groups        []TagID   `json:"groups"`
 	Default       bool      `json:"default"`
+	AutoEgress    bool      `json:"auto_egress"`
 }
 
 // APIEnrollmentKey - used to create enrollment keys via API
@@ -66,6 +67,7 @@ type APIEnrollmentKey struct {
 	Type          KeyType  `json:"type"`
 	Relay         string   `json:"relay"`
 	Groups        []TagID  `json:"groups"`
+	AutoEgress    bool     `json:"auto_egress"`
 }
 
 // RegisterResponse - the response to a successful enrollment register

+ 40 - 0
models/settings.go

@@ -0,0 +1,40 @@
+package models
+
+type Theme string
+
+const (
+	Dark   Theme = "dark"
+	Light  Theme = "light"
+	System Theme = "system"
+)
+
+type ServerSettings struct {
+	NetclientAutoUpdate        bool   `json:"netclientautoupdate"`
+	Verbosity                  int32  `json:"verbosity"`
+	AuthProvider               string `json:"authprovider"`
+	OIDCIssuer                 string `json:"oidcissuer"`
+	ClientID                   string `json:"client_id"`
+	ClientSecret               string `json:"client_secret"`
+	AzureTenant                string `json:"azure_tenant"`
+	Telemetry                  string `json:"telemetry"`
+	BasicAuth                  bool   `json:"basic_auth"`
+	JwtValidityDuration        int    `json:"jwt_validity_duration"`
+	RacAutoDisable             bool   `json:"rac_auto_disable"`
+	RacRestrictToSingleNetwork bool   `json:"rac_restrict_to_single_network"`
+	EndpointDetection          bool   `json:"endpoint_detection"`
+	AllowedEmailDomains        string `json:"allowed_email_domains"`
+	EmailSenderAddr            string `json:"email_sender_addr"`
+	EmailSenderUser            string `json:"email_sender_user"`
+	EmailSenderPassword        string `json:"email_sender_password"`
+	SmtpHost                   string `json:"smtp_host"`
+	SmtpPort                   int    `json:"smtp_port"`
+	MetricInterval             string `json:"metric_interval"`
+	MetricsPort                int    `json:"metrics_port"`
+	ManageDNS                  bool   `json:"manage_dns"`
+	DefaultDomain              string `json:"default_domain"`
+	Stun                       bool   `json:"stun"`
+	StunServers                string `json:"stun_servers"`
+	Theme                      Theme  `json:"theme"`
+	TextSize                   string `json:"text_size"`
+	ReducedMotion              bool   `json:"reduced_motion"`
+}

+ 1 - 0
models/structs.go

@@ -263,6 +263,7 @@ type NodeJoinResponse struct {
 type ServerConfig struct {
 	CoreDNSAddr       string `yaml:"corednsaddr"`
 	API               string `yaml:"api"`
+	APIHost           string `yaml:"apihost"`
 	APIPort           string `yaml:"apiport"`
 	DNSMode           string `yaml:"dnsmode"`
 	Version           string `yaml:"version"`

+ 12 - 0
models/user_mgmt.go

@@ -13,6 +13,7 @@ type RsrcID string
 type UserRoleID string
 type UserGroupID string
 type AuthType string
+type TokenType string
 
 var (
 	BasicAuth AuthType = "basic_auth"
@@ -35,6 +36,15 @@ func GetRAGRoleID(netID, hostID string) UserRoleID {
 	return UserRoleID(fmt.Sprintf("netID-%s-rag-%s", netID, hostID))
 }
 
+func (t TokenType) String() string {
+	return string(t)
+}
+
+var (
+	UserIDTokenType TokenType = "user_id_token"
+	AccessTokenType TokenType = "access_token"
+)
+
 var RsrcTypeMap = map[RsrcType]struct{}{
 	HostRsrc:           {},
 	RelayRsrc:          {},
@@ -187,6 +197,8 @@ type UserAuthParams struct {
 type UserClaims struct {
 	Role           UserRoleID
 	UserName       string
+	Api            string
+	TokenType      TokenType
 	RacAutoDisable bool
 	jwt.RegisteredClaims
 }

+ 1 - 1
mq/publishers.go

@@ -21,7 +21,7 @@ func PublishPeerUpdate(replacePeers bool) error {
 		return nil
 	}
 
-	if servercfg.GetManageDNS() {
+	if logic.GetManageDNS() {
 		sendDNSSync()
 	}
 

+ 3 - 3
pro/auth/auth.go

@@ -47,7 +47,7 @@ var (
 )
 
 func getCurrentAuthFunctions() map[string]interface{} {
-	var authInfo = servercfg.GetAuthProviderInfo()
+	var authInfo = logic.GetAuthProviderInfo(logic.GetServerSettings())
 	var authProvider = authInfo[0]
 	switch authProvider {
 	case google_provider_name:
@@ -74,7 +74,7 @@ func InitializeAuthProvider() string {
 	if err != nil {
 		logger.FatalLog("failed to set auth_secret", err.Error())
 	}
-	var authInfo = servercfg.GetAuthProviderInfo()
+	var authInfo = logic.GetAuthProviderInfo(logic.GetServerSettings())
 	var serverConn = servercfg.GetAPIHost()
 	if strings.Contains(serverConn, "localhost") || strings.Contains(serverConn, "127.0.0.1") {
 		serverConn = "http://" + serverConn
@@ -275,7 +275,7 @@ func isStateCached(state string) bool {
 
 // isEmailAllowed - checks if email is allowed to signup
 func isEmailAllowed(email string) bool {
-	allowedDomains := servercfg.GetAllowedEmailDomains()
+	allowedDomains := logic.GetAllowedEmailDomains()
 	domains := strings.Split(allowedDomains, ",")
 	if len(domains) == 1 && domains[0] == "*" {
 		return true

+ 1 - 1
pro/auth/azure-ad.go

@@ -35,7 +35,7 @@ func initAzureAD(redirectURL string, clientID string, clientSecret string) {
 		ClientID:     clientID,
 		ClientSecret: clientSecret,
 		Scopes:       []string{"User.Read", "email", "profile", "openid"},
-		Endpoint:     microsoft.AzureADEndpoint(servercfg.GetAzureTenant()),
+		Endpoint:     microsoft.AzureADEndpoint(logic.GetAzureTenant()),
 	}
 }
 

+ 7 - 7
pro/email/email.go

@@ -4,7 +4,7 @@ import (
 	"context"
 	"regexp"
 
-	"github.com/gravitl/netmaker/servercfg"
+	"github.com/gravitl/netmaker/logic"
 )
 
 type EmailSenderType string
@@ -16,14 +16,14 @@ const (
 	Resend EmailSenderType = "resend"
 )
 
-func init() {
+func Init() {
 
 	smtpSender := &SmtpSender{
-		SmtpHost:    servercfg.GetSmtpHost(),
-		SmtpPort:    servercfg.GetSmtpPort(),
-		SenderEmail: servercfg.GetSenderEmail(),
-		SendUser:    servercfg.GetSenderUser(),
-		SenderPass:  servercfg.GetEmaiSenderPassword(),
+		SmtpHost:    logic.GetSmtpHost(),
+		SmtpPort:    logic.GetSmtpPort(),
+		SenderEmail: logic.GetSenderEmail(),
+		SendUser:    logic.GetSenderUser(),
+		SenderPass:  logic.GetEmaiSenderPassword(),
 	}
 	if smtpSender.SendUser == "" {
 		smtpSender.SendUser = smtpSender.SenderEmail

+ 5 - 1
pro/initialize.go

@@ -13,6 +13,7 @@ import (
 	"github.com/gravitl/netmaker/mq"
 	"github.com/gravitl/netmaker/pro/auth"
 	proControllers "github.com/gravitl/netmaker/pro/controllers"
+	"github.com/gravitl/netmaker/pro/email"
 	proLogic "github.com/gravitl/netmaker/pro/logic"
 	"github.com/gravitl/netmaker/servercfg"
 	"golang.org/x/exp/slog"
@@ -79,7 +80,7 @@ func InitPro() {
 			addTrialLicenseHook()
 		}
 
-		if servercfg.GetServerConfig().RacAutoDisable {
+		if logic.GetRacAutoDisable() {
 			AddRacHooks()
 		}
 
@@ -94,6 +95,7 @@ func InitPro() {
 
 		proLogic.LoadNodeMetricsToCache()
 		proLogic.InitFailOverCache()
+		email.Init()
 	})
 	logic.ResetFailOver = proLogic.ResetFailOver
 	logic.ResetFailedOverPeer = proLogic.ResetFailedOverPeer
@@ -138,6 +140,8 @@ func InitPro() {
 	logic.GetUserGroupsInNetwork = proLogic.GetUserGroupsInNetwork
 	logic.GetUserGroup = proLogic.GetUserGroup
 	logic.GetNodeStatus = proLogic.GetNodeStatus
+	logic.InitializeAuthProvider = auth.InitializeAuthProvider
+	logic.EmailInit = email.Init
 }
 
 func retrieveProLogo() string {

+ 2 - 1
pro/license.go

@@ -9,11 +9,12 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	"github.com/gravitl/netmaker/utils"
 	"io"
 	"net/http"
 	"time"
 
+	"github.com/gravitl/netmaker/utils"
+
 	"golang.org/x/crypto/nacl/box"
 	"golang.org/x/exp/slog"
 

+ 1 - 2
pro/remote_access_client.go

@@ -10,7 +10,6 @@ import (
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/mq"
-	"github.com/gravitl/netmaker/servercfg"
 	"golang.org/x/exp/slog"
 )
 
@@ -41,7 +40,7 @@ func racAutoDisableHook() error {
 	}
 
 	currentTime := time.Now()
-	validityDuration := servercfg.GetJwtValidityDuration()
+	validityDuration := logic.GetJwtValidityDuration()
 	for _, user := range users {
 		if user.PlatformRoleID == models.AdminRole ||
 			user.PlatformRoleID == models.SuperAdminRole {

+ 4 - 9
schema/jobs.go → schema/job.go

@@ -16,21 +16,16 @@ import (
 // that it is easier to prevent a task from
 // being executed again.
 type Job struct {
-	ID        string    `gorm:"id;primary_key"`
-	CreatedAt time.Time `gorm:"created_at"`
-}
-
-// TableName returns the name of the jobs table.
-func (j *Job) TableName() string {
-	return "jobs"
+	ID        string `gorm:"primaryKey"`
+	CreatedAt time.Time
 }
 
 // Create creates a job record in the jobs table.
 func (j *Job) Create(ctx context.Context) error {
-	return db.FromContext(ctx).Table(j.TableName()).Create(j).Error
+	return db.FromContext(ctx).Model(&Job{}).Create(j).Error
 }
 
 // Get returns a job record with the given Job.ID.
 func (j *Job) Get(ctx context.Context) error {
-	return db.FromContext(ctx).Table(j.TableName()).Where("id = ?", j.ID).First(j).Error
+	return db.FromContext(ctx).Model(&Job{}).Where("id = ?", j.ID).First(j).Error
 }

+ 1 - 0
schema/models.go

@@ -4,5 +4,6 @@ package schema
 func ListModels() []interface{} {
 	return []interface{}{
 		&Job{},
+		&UserAccessToken{},
 	}
 }

+ 52 - 0
schema/user_access_token.go

@@ -0,0 +1,52 @@
+package schema
+
+import (
+	"context"
+	"time"
+
+	"github.com/gravitl/netmaker/db"
+)
+
+// UserAccessToken - token used to access netmaker
+type UserAccessToken struct {
+	ID        string    `gorm:"primaryKey" json:"id"`
+	Name      string    `json:"name"`
+	UserName  string    `json:"user_name"`
+	ExpiresAt time.Time `json:"expires_at"`
+	LastUsed  time.Time `json:"last_used"`
+	CreatedBy string    `json:"created_by"`
+	CreatedAt time.Time `json:"created_at"`
+}
+
+func (a *UserAccessToken) Get(ctx context.Context) error {
+	return db.FromContext(ctx).Model(&UserAccessToken{}).First(&a).Where("id = ?", a.ID).Error
+}
+
+func (a *UserAccessToken) Update(ctx context.Context) error {
+	return db.FromContext(ctx).Model(&UserAccessToken{}).Where("id = ?", a.ID).Updates(&a).Error
+}
+
+func (a *UserAccessToken) Create(ctx context.Context) error {
+	return db.FromContext(ctx).Model(&UserAccessToken{}).Create(&a).Error
+}
+
+func (a *UserAccessToken) List(ctx context.Context) (ats []UserAccessToken, err error) {
+	err = db.FromContext(ctx).Model(&UserAccessToken{}).Find(&ats).Error
+	return
+}
+
+func (a *UserAccessToken) ListByUser(ctx context.Context) (ats []UserAccessToken) {
+	db.FromContext(ctx).Model(&UserAccessToken{}).Where("user_name = ?", a.UserName).Find(&ats)
+	if ats == nil {
+		ats = []UserAccessToken{}
+	}
+	return
+}
+
+func (a *UserAccessToken) Delete(ctx context.Context) error {
+	return db.FromContext(ctx).Model(&UserAccessToken{}).Where("id = ?", a.ID).Delete(&a).Error
+}
+
+func (a *UserAccessToken) DeleteAllUserTokens(ctx context.Context) error {
+	return db.FromContext(ctx).Model(&UserAccessToken{}).Where("user_name = ?", a.UserName).Delete(&a).Error
+}

+ 16 - 35
servercfg/serverconf.go

@@ -2,6 +2,7 @@ package servercfg
 
 import (
 	"errors"
+	"fmt"
 	"io"
 	"net/http"
 	"os"
@@ -11,11 +12,8 @@ import (
 	"time"
 
 	"github.com/gravitl/netmaker/config"
-	"github.com/gravitl/netmaker/models"
 )
 
-var ServerInfo = GetServerInfo()
-
 // EmqxBrokerType denotes the broker type for EMQX MQTT
 const EmqxBrokerType = "emqx"
 
@@ -116,6 +114,18 @@ func GetJwtValidityDuration() time.Duration {
 	return defaultDuration
 }
 
+// GetJwtValidityDuration - returns the JWT validity duration in seconds
+func GetJwtValidityDurationFromEnv() int {
+	var defaultDuration = 43200
+	if os.Getenv("JWT_VALIDITY_DURATION") != "" {
+		t, err := strconv.Atoi(os.Getenv("JWT_VALIDITY_DURATION"))
+		if err == nil {
+			return t
+		}
+	}
+	return defaultDuration
+}
+
 // GetRacAutoDisable - returns whether the feature to autodisable RAC is enabled
 func GetRacAutoDisable() bool {
 	return os.Getenv("RAC_AUTO_DISABLE") == "true"
@@ -126,38 +136,6 @@ func GetRacRestrictToSingleNetwork() bool {
 	return os.Getenv("RAC_RESTRICT_TO_SINGLE_NETWORK") == "true"
 }
 
-// GetServerInfo - gets the server config into memory from file or env
-func GetServerInfo() models.ServerConfig {
-	var cfg models.ServerConfig
-	cfg.Server = GetServer()
-	if GetBrokerType() == EmqxBrokerType {
-		cfg.MQUserName = "HOST_ID"
-		cfg.MQPassword = "HOST_PASS"
-	} else {
-		cfg.MQUserName = GetMqUserName()
-		cfg.MQPassword = GetMqPassword()
-	}
-	cfg.API = GetAPIConnString()
-	cfg.CoreDNSAddr = GetCoreDNSAddr()
-	cfg.APIPort = GetAPIPort()
-	cfg.DNSMode = "off"
-	cfg.Broker = GetPublicBrokerEndpoint()
-	cfg.BrokerType = GetBrokerType()
-	if IsDNSMode() {
-		cfg.DNSMode = "on"
-	}
-	cfg.Version = GetVersion()
-	cfg.IsPro = IsPro
-	cfg.MetricInterval = GetMetricInterval()
-	cfg.MetricsPort = GetMetricsPort()
-	cfg.ManageDNS = GetManageDNS()
-	cfg.Stun = IsStunEnabled()
-	cfg.StunServers = GetStunServers()
-	cfg.DefaultDomain = GetDefaultDomain()
-	cfg.EndpointDetection = IsEndpointDetectionEnabled()
-	return cfg
-}
-
 // GetFrontendURL - gets the frontend url
 func GetFrontendURL() string {
 	var frontend = ""
@@ -166,6 +144,9 @@ func GetFrontendURL() string {
 	} else if config.Config.Server.FrontendURL != "" {
 		frontend = config.Config.Server.FrontendURL
 	}
+	if frontend == "" {
+		return fmt.Sprintf("https://dashboard.%s", GetNmBaseDomain())
+	}
 	return frontend
 }