Răsfoiți Sursa

Merge branch 'develop' of https://github.com/gravitl/netmaker into ACC-638

abhishek9686 1 an în urmă
părinte
comite
b3a9ffd260

+ 7 - 2
.github/workflows/branchtest.yml

@@ -2,6 +2,11 @@ name: Deploy and Test Branch
 
 
 on:
 on:
   workflow_dispatch:
   workflow_dispatch:
+    inputs:
+      branches:
+        description: 'Branch to deploy and test'
+        required: true
+        default: 'develop'
   pull_request:
   pull_request:
       types: [opened, synchronize, reopened]
       types: [opened, synchronize, reopened]
       branches: [develop]
       branches: [develop]
@@ -28,7 +33,7 @@ jobs:
         uses: actions/checkout@v4
         uses: actions/checkout@v4
         with:
         with:
           repository: gravitl/netclient
           repository: gravitl/netclient
-          ref: develop
+          ref: ${{ github.event_name == 'workflow_dispatch' && inputs.branch || 'develop' }}
       - name: check if branch exists
       - name: check if branch exists
         id: getbranch 
         id: getbranch 
         run: |
         run: |
@@ -45,6 +50,6 @@ jobs:
     needs: [getbranch, skip-check]
     needs: [getbranch, skip-check]
     with:
     with:
       netclientbranch: ${{ needs.getbranch.outputs.netclientbranch }}
       netclientbranch: ${{ needs.getbranch.outputs.netclientbranch }}
-      netmakerbranch: ${{ github.head_ref }}
+      netmakerbranch: ${{ github.event_name == 'workflow_dispatch' && inputs.branch || github.head_ref }}
       tag: ${{ github.run_id }}-${{ github.run_attempt }}
       tag: ${{ github.run_id }}-${{ github.run_attempt }}
     secrets: inherit          
     secrets: inherit          

+ 38 - 8
.github/workflows/deletedroplets.yml

@@ -37,13 +37,28 @@ jobs:
       - name: delete droplets
       - name: delete droplets
         if: success() || failure()
         if: success() || failure()
         run: |
         run: |
-          sleep 15m
-          curl -X DELETE \
+          sleep 1m
+          response=$(curl -X DELETE \
             -H "Content-Type: application/json" \
             -H "Content-Type: application/json" \
             -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \
             -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \
-            "https://api.digitalocean.com/v2/droplets?tag_name=$TAG"
+            -w "\n%{http_code}" \
+            "https://api.digitalocean.com/v2/droplets?tag_name=$TAG")
+          
+          status_code=$(echo "$response" | tail -n1)
+          body=$(echo "$response" | sed '$d')
+          
+          echo "Response body: $body"
+          echo "Status code: $status_code"
+          
+          if [ "$status_code" -eq 204 ]; then
+            echo "Droplets deleted successfully"
+          else
+            echo "Failed to delete droplets. Status code: $status_code"
+            exit 1
+          fi
+          sleep 1m
         env:
         env:
-          DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }}
+          DIGITALOCEAN_TOKEN: ${{ secrets.DO_TEST_TOKEN }}
           TAG: ${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }}
           TAG: ${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }}
       - name: mark server as available
       - name: mark server as available
         if: success() || failure()
         if: success() || failure()
@@ -94,13 +109,28 @@ jobs:
       - name: delete droplets
       - name: delete droplets
         if: success() || failure()
         if: success() || failure()
         run: |
         run: |
-          sleep 3h
-          curl -X DELETE \
+          sleep 1m
+          response=$(curl -X DELETE \
             -H "Content-Type: application/json" \
             -H "Content-Type: application/json" \
             -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \
             -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \
-            "https://api.digitalocean.com/v2/droplets?tag_name=$TAG"
+            -w "\n%{http_code}" \
+            "https://api.digitalocean.com/v2/droplets?tag_name=$TAG")
+          
+          status_code=$(echo "$response" | tail -n1)
+          body=$(echo "$response" | sed '$d')
+          
+          echo "Response body: $body"
+          echo "Status code: $status_code"
+          
+          if [ "$status_code" -eq 204 ]; then
+            echo "Droplets deleted successfully"
+          else
+            echo "Failed to delete droplets. Status code: $status_code"
+            exit 1
+          fi
+          sleep 1m
         env:
         env:
-          DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }}
+          DIGITALOCEAN_TOKEN: ${{ secrets.DO_TEST_TOKEN }}
           TAG: ${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }}
           TAG: ${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }}
       - name: mark server as available
       - name: mark server as available
         if: success() || failure()
         if: success() || failure()

+ 29 - 4
cli/cmd/user/create.go

@@ -1,6 +1,8 @@
 package user
 package user
 
 
 import (
 import (
+	"strings"
+
 	"github.com/gravitl/netmaker/cli/functions"
 	"github.com/gravitl/netmaker/cli/functions"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
@@ -12,18 +14,41 @@ var userCreateCmd = &cobra.Command{
 	Short: "Create a new user",
 	Short: "Create a new user",
 	Long:  `Create a new user`,
 	Long:  `Create a new user`,
 	Run: func(cmd *cobra.Command, args []string) {
 	Run: func(cmd *cobra.Command, args []string) {
-		user := &models.User{UserName: username, Password: password, IsAdmin: admin}
+		user := &models.User{UserName: username, Password: password, PlatformRoleID: models.UserRoleID(platformID)}
+		if len(networkRoles) > 0 {
+			netRolesMap := make(map[models.NetworkID]map[models.UserRoleID]struct{})
+			for netID, netRoles := range networkRoles {
+				roleMap := make(map[models.UserRoleID]struct{})
+				for _, roleID := range strings.Split(netRoles, " ") {
+					roleMap[models.UserRoleID(roleID)] = struct{}{}
+				}
+				netRolesMap[models.NetworkID(netID)] = roleMap
+			}
+			user.NetworkRoles = netRolesMap
+		}
+		if len(groups) > 0 {
+			grMap := make(map[models.UserGroupID]struct{})
+			for _, groupID := range groups {
+				grMap[models.UserGroupID(groupID)] = struct{}{}
+			}
+			user.UserGroups = grMap
+		}
+
 		functions.PrettyPrint(functions.CreateUser(user))
 		functions.PrettyPrint(functions.CreateUser(user))
 	},
 	},
 }
 }
 
 
 func init() {
 func init() {
+
 	userCreateCmd.Flags().StringVar(&username, "name", "", "Name of the user")
 	userCreateCmd.Flags().StringVar(&username, "name", "", "Name of the user")
 	userCreateCmd.Flags().StringVar(&password, "password", "", "Password of the user")
 	userCreateCmd.Flags().StringVar(&password, "password", "", "Password of the user")
+	userCreateCmd.Flags().StringVarP(&platformID, "platform-role", "r", models.ServiceUser.String(),
+		"Platform Role of the user; run `nmctl roles list` to see available user roles")
 	userCreateCmd.MarkFlagRequired("name")
 	userCreateCmd.MarkFlagRequired("name")
 	userCreateCmd.MarkFlagRequired("password")
 	userCreateCmd.MarkFlagRequired("password")
-	userCreateCmd.Flags().BoolVar(&admin, "admin", false, "Make the user an admin ?")
-	userCreateCmd.Flags().StringVar(&networks, "networks", "", "List of networks the user will access to (comma separated)")
-	userCreateCmd.Flags().StringVar(&groups, "groups", "", "List of user groups the user will be part of (comma separated)")
+	userCreateCmd.PersistentFlags().StringToStringVarP(&networkRoles, "network-roles", "n", nil,
+		"Mapping of networkID and list of roles user will be part of (comma separated)")
+	userCreateCmd.Flags().BoolVar(&admin, "admin", false, "Make the user an admin ? (deprecated v0.25.0 onwards)")
+	userCreateCmd.Flags().StringArrayVarP(&groups, "groups", "g", nil, "List of user groups the user will be part of (comma separated)")
 	rootCmd.AddCommand(userCreateCmd)
 	rootCmd.AddCommand(userCreateCmd)
 }
 }

+ 6 - 5
cli/cmd/user/flags.go

@@ -1,9 +1,10 @@
 package user
 package user
 
 
 var (
 var (
-	username string
-	password string
-	admin    bool
-	networks string
-	groups   string
+	username     string
+	password     string
+	platformID   string
+	admin        bool
+	networkRoles map[string]string
+	groups       []string
 )
 )

+ 118 - 0
cli/cmd/user/groups.go

@@ -0,0 +1,118 @@
+package user
+
+import (
+	"fmt"
+	"os"
+	"strings"
+
+	"github.com/gravitl/netmaker/cli/cmd/commons"
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/guumaster/tablewriter"
+	"github.com/spf13/cobra"
+)
+
+var userGroupCmd = &cobra.Command{
+	Use:   "group",
+	Args:  cobra.NoArgs,
+	Short: "Manage User Groups",
+	Long:  `Manage User Groups`,
+}
+
+var userGroupListCmd = &cobra.Command{
+	Use:   "list",
+	Args:  cobra.NoArgs,
+	Short: "List all user groups",
+	Long:  `List all user groups`,
+	Run: func(cmd *cobra.Command, args []string) {
+		data := functions.ListUserGrps()
+		switch commons.OutputFormat {
+		case commons.JsonOutput:
+			functions.PrettyPrint(data)
+		default:
+			table := tablewriter.NewWriter(os.Stdout)
+			h := []string{"ID", "MetaData", "Network Roles"}
+			table.SetHeader(h)
+			for _, d := range data {
+
+				roleInfoStr := ""
+				for netID, netRoleMap := range d.NetworkRoles {
+					roleList := []string{}
+					for roleID := range netRoleMap {
+						roleList = append(roleList, roleID.String())
+					}
+					roleInfoStr += fmt.Sprintf("[%s]: %s", netID, strings.Join(roleList, ","))
+				}
+				e := []string{d.ID.String(), d.MetaData, roleInfoStr}
+				table.Append(e)
+			}
+			table.Render()
+		}
+	},
+}
+
+var userGroupCreateCmd = &cobra.Command{
+	Use:   "create",
+	Args:  cobra.NoArgs,
+	Short: "create user group",
+	Long:  `create user group`,
+	Run: func(cmd *cobra.Command, args []string) {
+		fmt.Println("CLI doesn't support creation of groups currently. Visit the dashboard to create one or refer to our api documentation https://docs.v2.netmaker.io/reference")
+	},
+}
+
+var userGroupDeleteCmd = &cobra.Command{
+	Use:   "delete [groupID]",
+	Args:  cobra.ExactArgs(1),
+	Short: "delete user group",
+	Long:  `delete user group`,
+	Run: func(cmd *cobra.Command, args []string) {
+		resp := functions.DeleteUserGrp(args[0])
+		if resp != nil {
+			fmt.Println(resp.Message)
+		}
+	},
+}
+
+var userGroupGetCmd = &cobra.Command{
+	Use:   "get [groupID]",
+	Args:  cobra.ExactArgs(1),
+	Short: "get user group",
+	Long:  `get user group`,
+	Run: func(cmd *cobra.Command, args []string) {
+		data := functions.GetUserGrp(args[0])
+		switch commons.OutputFormat {
+		case commons.JsonOutput:
+			functions.PrettyPrint(data)
+		default:
+			table := tablewriter.NewWriter(os.Stdout)
+			h := []string{"ID", "MetaData", "Network Roles"}
+			table.SetHeader(h)
+			roleInfoStr := ""
+			for netID, netRoleMap := range data.NetworkRoles {
+				roleList := []string{}
+				for roleID := range netRoleMap {
+					roleList = append(roleList, roleID.String())
+				}
+				roleInfoStr += fmt.Sprintf("[%s]: %s", netID, strings.Join(roleList, ","))
+			}
+			e := []string{data.ID.String(), data.MetaData, roleInfoStr}
+			table.Append(e)
+			table.Render()
+		}
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(userGroupCmd)
+	// list roles cmd
+	userGroupCmd.AddCommand(userGroupListCmd)
+
+	// create roles cmd
+	userGroupCmd.AddCommand(userGroupCreateCmd)
+
+	// delete role cmd
+	userGroupCmd.AddCommand(userGroupDeleteCmd)
+
+	// Get Role
+	userGroupCmd.AddCommand(userGroupGetCmd)
+}

+ 7 - 3
cli/cmd/user/list.go

@@ -2,7 +2,7 @@ package user
 
 
 import (
 import (
 	"os"
 	"os"
-	"strconv"
+	"strings"
 
 
 	"github.com/gravitl/netmaker/cli/cmd/commons"
 	"github.com/gravitl/netmaker/cli/cmd/commons"
 	"github.com/gravitl/netmaker/cli/functions"
 	"github.com/gravitl/netmaker/cli/functions"
@@ -22,9 +22,13 @@ var userListCmd = &cobra.Command{
 			functions.PrettyPrint(data)
 			functions.PrettyPrint(data)
 		default:
 		default:
 			table := tablewriter.NewWriter(os.Stdout)
 			table := tablewriter.NewWriter(os.Stdout)
-			table.SetHeader([]string{"Name", "SuperAdmin", "Admin"})
+			table.SetHeader([]string{"Name", "Platform Role", "Groups"})
 			for _, d := range *data {
 			for _, d := range *data {
-				table.Append([]string{d.UserName, strconv.FormatBool(d.IsSuperAdmin), strconv.FormatBool(d.IsAdmin)})
+				g := []string{}
+				for gID := range d.UserGroups {
+					g = append(g, gID.String())
+				}
+				table.Append([]string{d.UserName, d.PlatformRoleID.String(), strings.Join(g, ",")})
 			}
 			}
 			table.Render()
 			table.Render()
 		}
 		}

+ 121 - 0
cli/cmd/user/roles.go

@@ -0,0 +1,121 @@
+package user
+
+import (
+	"fmt"
+	"os"
+	"strconv"
+
+	"github.com/gravitl/netmaker/cli/cmd/commons"
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/guumaster/tablewriter"
+	"github.com/spf13/cobra"
+)
+
+var userRoleCmd = &cobra.Command{
+	Use:   "role",
+	Args:  cobra.NoArgs,
+	Short: "Manage User Roles",
+	Long:  `Manage User Roles`,
+}
+
+// List Roles
+var (
+	platformRoles bool
+)
+var userRoleListCmd = &cobra.Command{
+	Use:   "list",
+	Args:  cobra.NoArgs,
+	Short: "List all user roles",
+	Long:  `List all user roles`,
+	Run: func(cmd *cobra.Command, args []string) {
+		data := functions.ListUserRoles()
+		switch commons.OutputFormat {
+		case commons.JsonOutput:
+			functions.PrettyPrint(data)
+		default:
+			table := tablewriter.NewWriter(os.Stdout)
+			h := []string{"ID", "Default", "Dashboard Access", "Full Access"}
+
+			if !platformRoles {
+				h = append(h, "Network")
+			}
+			table.SetHeader(h)
+			for _, d := range data {
+				e := []string{d.ID.String(), strconv.FormatBool(d.Default), strconv.FormatBool(d.DenyDashboardAccess), strconv.FormatBool(d.FullAccess)}
+				if !platformRoles {
+					e = append(e, d.NetworkID.String())
+				}
+				table.Append(e)
+			}
+			table.Render()
+		}
+	},
+}
+
+var userRoleCreateCmd = &cobra.Command{
+	Use:   "create",
+	Args:  cobra.NoArgs,
+	Short: "create user role",
+	Long:  `create user role`,
+	Run: func(cmd *cobra.Command, args []string) {
+		fmt.Println("CLI doesn't support creation of roles currently. Visit the dashboard to create one or refer to our api documentation https://docs.v2.netmaker.io/reference")
+	},
+}
+
+var userRoleDeleteCmd = &cobra.Command{
+	Use:   "delete [roleID]",
+	Args:  cobra.ExactArgs(1),
+	Short: "delete user role",
+	Long:  `delete user role`,
+	Run: func(cmd *cobra.Command, args []string) {
+		resp := functions.DeleteUserRole(args[0])
+		if resp != nil {
+			fmt.Println(resp.Message)
+		}
+	},
+}
+
+var userRoleGetCmd = &cobra.Command{
+	Use:   "get [roleID]",
+	Args:  cobra.ExactArgs(1),
+	Short: "get user role",
+	Long:  `get user role`,
+	Run: func(cmd *cobra.Command, args []string) {
+		d := functions.GetUserRole(args[0])
+		switch commons.OutputFormat {
+		case commons.JsonOutput:
+			functions.PrettyPrint(d)
+		default:
+			table := tablewriter.NewWriter(os.Stdout)
+			h := []string{"ID", "Default Role", "Dashboard Access", "Full Access"}
+
+			if d.NetworkID != "" {
+				h = append(h, "Network")
+			}
+			table.SetHeader(h)
+			e := []string{d.ID.String(), strconv.FormatBool(d.Default), strconv.FormatBool(!d.DenyDashboardAccess), strconv.FormatBool(d.FullAccess)}
+			if !platformRoles {
+				e = append(e, d.NetworkID.String())
+			}
+			table.Append(e)
+			table.Render()
+		}
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(userRoleCmd)
+	// list roles cmd
+	userRoleListCmd.Flags().BoolVar(&platformRoles, "platform-roles", true,
+		"set to false to list network roles. By default it will only list platform roles")
+	userRoleCmd.AddCommand(userRoleListCmd)
+
+	// create roles cmd
+	userRoleCmd.AddCommand(userRoleCreateCmd)
+
+	// delete role cmd
+	userRoleCmd.AddCommand(userRoleDeleteCmd)
+
+	// Get Role
+	userRoleCmd.AddCommand(userRoleGetCmd)
+}

+ 32 - 4
cli/cmd/user/update.go

@@ -1,6 +1,8 @@
 package user
 package user
 
 
 import (
 import (
+	"strings"
+
 	"github.com/gravitl/netmaker/cli/functions"
 	"github.com/gravitl/netmaker/cli/functions"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
@@ -12,14 +14,40 @@ var userUpdateCmd = &cobra.Command{
 	Short: "Update a user",
 	Short: "Update a user",
 	Long:  `Update a user`,
 	Long:  `Update a user`,
 	Run: func(cmd *cobra.Command, args []string) {
 	Run: func(cmd *cobra.Command, args []string) {
-		user := &models.User{UserName: args[0], IsAdmin: admin}
+		user := &models.User{UserName: args[0]}
+		if platformID != "" {
+			user.PlatformRoleID = models.UserRoleID(platformID)
+		}
+		if len(networkRoles) > 0 {
+			netRolesMap := make(map[models.NetworkID]map[models.UserRoleID]struct{})
+			for netID, netRoles := range networkRoles {
+				roleMap := make(map[models.UserRoleID]struct{})
+				for _, roleID := range strings.Split(netRoles, ",") {
+					roleMap[models.UserRoleID(roleID)] = struct{}{}
+				}
+				netRolesMap[models.NetworkID(netID)] = roleMap
+			}
+			user.NetworkRoles = netRolesMap
+		}
+		if len(groups) > 0 {
+			grMap := make(map[models.UserGroupID]struct{})
+			for _, groupID := range groups {
+				grMap[models.UserGroupID(groupID)] = struct{}{}
+			}
+			user.UserGroups = grMap
+		}
 		functions.PrettyPrint(functions.UpdateUser(user))
 		functions.PrettyPrint(functions.UpdateUser(user))
 	},
 	},
 }
 }
 
 
 func init() {
 func init() {
-	userUpdateCmd.Flags().BoolVar(&admin, "admin", false, "Make the user an admin ?")
-	userUpdateCmd.Flags().StringVar(&networks, "networks", "", "List of networks the user will access to (comma separated)")
-	userUpdateCmd.Flags().StringVar(&groups, "groups", "", "List of user groups the user will be part of (comma separated)")
+
+	userUpdateCmd.Flags().StringVar(&password, "password", "", "Password of the user")
+	userUpdateCmd.Flags().StringVarP(&platformID, "platform-role", "r", "",
+		"Platform Role of the user; run `nmctl roles list` to see available user roles")
+	userUpdateCmd.PersistentFlags().StringToStringVarP(&networkRoles, "network-roles", "n", nil,
+		"Mapping of networkID and list of roles user will be part of (comma separated)")
+	userUpdateCmd.Flags().BoolVar(&admin, "admin", false, "Make the user an admin ? (deprecated v0.25.0 onwards)")
+	userUpdateCmd.Flags().StringArrayVarP(&groups, "groups", "g", nil, "List of user groups the user will be part of (comma separated)")
 	rootCmd.AddCommand(userUpdateCmd)
 	rootCmd.AddCommand(userUpdateCmd)
 }
 }

+ 1 - 1
cli/config/config.go

@@ -86,7 +86,7 @@ func GetCurrentContext() (name string, ctx Context) {
 			return
 			return
 		}
 		}
 	}
 	}
-	log.Fatalf("No current context set, do so via `netmaker context use <name>`")
+	log.Fatalf("No current context set, do so via `nmctl context use <name>`")
 	return
 	return
 }
 }
 
 

+ 38 - 1
cli/functions/user.go

@@ -1,6 +1,8 @@
 package functions
 package functions
 
 
 import (
 import (
+	"encoding/json"
+	"fmt"
 	"net/http"
 	"net/http"
 
 
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
@@ -18,7 +20,7 @@ func CreateUser(payload *models.User) *models.User {
 
 
 // UpdateUser - update a user
 // UpdateUser - update a user
 func UpdateUser(payload *models.User) *models.User {
 func UpdateUser(payload *models.User) *models.User {
-	return request[models.User](http.MethodPut, "/api/users/networks/"+payload.UserName, payload)
+	return request[models.User](http.MethodPut, "/api/users/"+payload.UserName, payload)
 }
 }
 
 
 // DeleteUser - delete a user
 // DeleteUser - delete a user
@@ -35,3 +37,38 @@ func GetUser(username string) *models.User {
 func ListUsers() *[]models.ReturnUser {
 func ListUsers() *[]models.ReturnUser {
 	return request[[]models.ReturnUser](http.MethodGet, "/api/users", nil)
 	return request[[]models.ReturnUser](http.MethodGet, "/api/users", nil)
 }
 }
+
+func ListUserRoles() (roles []models.UserRolePermissionTemplate) {
+	resp := request[models.SuccessResponse](http.MethodGet, "/api/v1/users/roles", nil)
+	d, _ := json.Marshal(resp.Response)
+	json.Unmarshal(d, &roles)
+	return
+}
+
+func DeleteUserRole(roleID string) *models.SuccessResponse {
+	return request[models.SuccessResponse](http.MethodDelete, fmt.Sprintf("/api/v1/users/role?role_id=%s", roleID), nil)
+}
+func GetUserRole(roleID string) (role models.UserRolePermissionTemplate) {
+	resp := request[models.SuccessResponse](http.MethodGet, fmt.Sprintf("/api/v1/users/role?role_id=%s", roleID), nil)
+	d, _ := json.Marshal(resp.Response)
+	json.Unmarshal(d, &role)
+	return
+}
+
+func ListUserGrps() (groups []models.UserGroup) {
+	resp := request[models.SuccessResponse](http.MethodGet, "/api/v1/users/groups", nil)
+	d, _ := json.Marshal(resp.Response)
+	json.Unmarshal(d, &groups)
+	return
+}
+
+func DeleteUserGrp(grpID string) *models.SuccessResponse {
+	return request[models.SuccessResponse](http.MethodDelete, fmt.Sprintf("/api/v1/users/group?group_id=%s", grpID), nil)
+}
+
+func GetUserGrp(grpID string) (group models.UserGroup) {
+	resp := request[models.SuccessResponse](http.MethodGet, fmt.Sprintf("/api/v1/users/group?group_id=%s", grpID), nil)
+	d, _ := json.Marshal(resp.Response)
+	json.Unmarshal(d, &group)
+	return
+}

+ 6 - 2
controllers/middleware.go

@@ -6,7 +6,6 @@ import (
 	"strings"
 	"strings"
 
 
 	"github.com/gorilla/mux"
 	"github.com/gorilla/mux"
-	"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"
 )
 )
@@ -19,6 +18,12 @@ func userMiddleWare(handler http.Handler) http.Handler {
 			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 			return
 			return
 		}
 		}
+		if r.Method == http.MethodPost && route == "/api/extclients/{network}/{nodeid}" {
+			node, err := logic.GetNodeByID(params["nodeid"])
+			if err == nil {
+				params["network"] = node.Network
+			}
+		}
 		r.Header.Set("IS_GLOBAL_ACCESS", "no")
 		r.Header.Set("IS_GLOBAL_ACCESS", "no")
 		r.Header.Set("TARGET_RSRC", "")
 		r.Header.Set("TARGET_RSRC", "")
 		r.Header.Set("RSRC_TYPE", "")
 		r.Header.Set("RSRC_TYPE", "")
@@ -99,7 +104,6 @@ func userMiddleWare(handler http.Handler) http.Handler {
 		}
 		}
 
 
 		r.Header.Set("RSRC_TYPE", r.Header.Get("TARGET_RSRC"))
 		r.Header.Set("RSRC_TYPE", r.Header.Get("TARGET_RSRC"))
-		logger.Log(0, "URL ------> ", route)
 		handler.ServeHTTP(w, r)
 		handler.ServeHTTP(w, r)
 	})
 	})
 }
 }

+ 27 - 0
controllers/user.go

@@ -23,6 +23,8 @@ var (
 	upgrader = websocket.Upgrader{}
 	upgrader = websocket.Upgrader{}
 )
 )
 
 
+var ListRoles = listRoles
+
 func userHandlers(r *mux.Router) {
 func userHandlers(r *mux.Router) {
 	r.HandleFunc("/api/users/adm/hassuperadmin", hasSuperAdmin).Methods(http.MethodGet)
 	r.HandleFunc("/api/users/adm/hassuperadmin", hasSuperAdmin).Methods(http.MethodGet)
 	r.HandleFunc("/api/users/adm/createsuperadmin", createSuperAdmin).Methods(http.MethodPost)
 	r.HandleFunc("/api/users/adm/createsuperadmin", createSuperAdmin).Methods(http.MethodPost)
@@ -35,6 +37,7 @@ func userHandlers(r *mux.Router) {
 	r.HandleFunc("/api/users/{username}", logic.SecurityCheck(false, logic.ContinueIfUserMatch(http.HandlerFunc(getUser)))).Methods(http.MethodGet)
 	r.HandleFunc("/api/users/{username}", logic.SecurityCheck(false, logic.ContinueIfUserMatch(http.HandlerFunc(getUser)))).Methods(http.MethodGet)
 	r.HandleFunc("/api/v1/users", logic.SecurityCheck(false, logic.ContinueIfUserMatch(http.HandlerFunc(getUserV1)))).Methods(http.MethodGet)
 	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/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)
 
 
 }
 }
 
 
@@ -407,6 +410,9 @@ func createUser(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		return
 		return
 	}
 	}
+	if !servercfg.IsPro {
+		user.PlatformRoleID = models.AdminRole
+	}
 
 
 	if user.PlatformRoleID == "" {
 	if user.PlatformRoleID == "" {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("platform role is missing"), "badrequest"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("platform role is missing"), "badrequest"))
@@ -710,3 +716,24 @@ func socketHandler(w http.ResponseWriter, r *http.Request) {
 	// Start handling the session
 	// Start handling the session
 	go auth.SessionHandler(conn)
 	go auth.SessionHandler(conn)
 }
 }
+
+// @Summary     lists all user roles.
+// @Router      /api/v1/user/roles [get]
+// @Tags        Users
+// @Param       role_id param string true "roleid required to get the role details"
+// @Success     200 {object}  []models.UserRolePermissionTemplate
+// @Failure     500 {object} models.ErrorResponse
+func listRoles(w http.ResponseWriter, r *http.Request) {
+	var roles []models.UserRolePermissionTemplate
+	var err error
+	roles, err = logic.ListPlatformRoles()
+	if err != nil {
+		logic.ReturnErrorResponse(w, r, models.ErrorResponse{
+			Code:    http.StatusInternalServerError,
+			Message: err.Error(),
+		})
+		return
+	}
+
+	logic.ReturnSuccessResponseWithJson(w, r, roles, "successfully fetched user roles permission templates")
+}

+ 1 - 2
go.mod

@@ -38,11 +38,9 @@ require (
 )
 )
 
 
 require (
 require (
-	github.com/go-jose/go-jose/v3 v3.0.3
 	github.com/guumaster/tablewriter v0.0.10
 	github.com/guumaster/tablewriter v0.0.10
 	github.com/matryer/is v1.4.1
 	github.com/matryer/is v1.4.1
 	github.com/olekukonko/tablewriter v0.0.5
 	github.com/olekukonko/tablewriter v0.0.5
-	github.com/resendlabs/resend-go v1.7.0
 	github.com/spf13/cobra v1.8.1
 	github.com/spf13/cobra v1.8.1
 	gopkg.in/mail.v2 v2.3.1
 	gopkg.in/mail.v2 v2.3.1
 )
 )
@@ -50,6 +48,7 @@ require (
 require (
 require (
 	cloud.google.com/go/compute/metadata v0.3.0 // indirect
 	cloud.google.com/go/compute/metadata v0.3.0 // indirect
 	github.com/gabriel-vasile/mimetype v1.4.3 // indirect
 	github.com/gabriel-vasile/mimetype v1.4.3 // indirect
+	github.com/go-jose/go-jose/v3 v3.0.3 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/rivo/uniseg v0.2.0 // indirect
 	github.com/rivo/uniseg v0.2.0 // indirect
 	github.com/seancfoley/bintree v1.3.1 // indirect
 	github.com/seancfoley/bintree v1.3.1 // indirect

+ 0 - 12
go.sum

@@ -14,8 +14,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/eclipse/paho.mqtt.golang v1.4.3 h1:2kwcUGn8seMUfWndX0hGbvH8r7crgcJguQNCyp70xik=
 github.com/eclipse/paho.mqtt.golang v1.4.3 h1:2kwcUGn8seMUfWndX0hGbvH8r7crgcJguQNCyp70xik=
 github.com/eclipse/paho.mqtt.golang v1.4.3/go.mod h1:CSYvoAlsMkhYOXh/oKyxa8EcBci6dVkLCbo5tTC1RIE=
 github.com/eclipse/paho.mqtt.golang v1.4.3/go.mod h1:CSYvoAlsMkhYOXh/oKyxa8EcBci6dVkLCbo5tTC1RIE=
-github.com/eclipse/paho.mqtt.golang v1.5.0 h1:EH+bUVJNgttidWFkLLVKaQPGmkTUfQQqjOsyvMGvD6o=
-github.com/eclipse/paho.mqtt.golang v1.5.0/go.mod h1:du/2qNQVqJf/Sqs4MEL77kR8QTqANF7XU7Fk0aOTAgk=
 github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
 github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
 github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
 github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
 github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
 github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
@@ -65,10 +63,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0 h1:Y2hUrkfuM0on62KZOci/VLijlkdF/yeWU262BQgvcjE=
 github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0 h1:Y2hUrkfuM0on62KZOci/VLijlkdF/yeWU262BQgvcjE=
 github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0/go.mod h1:oa2sAs9tGai3VldabTV0eWejt/O4/OOD7azP8GaikqU=
 github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0/go.mod h1:oa2sAs9tGai3VldabTV0eWejt/O4/OOD7azP8GaikqU=
-github.com/posthog/posthog-go v1.2.18 h1:2CBA0LOB0up+gon+xpeXuhFw69gZpjAYxQoBBGwiDWw=
-github.com/posthog/posthog-go v1.2.18/go.mod h1:QjlpryJtfYLrZF2GUkAhejH4E7WlDbdKkvOi5hLmkdg=
-github.com/resendlabs/resend-go v1.7.0 h1:DycOqSXtw2q7aB+Nt9DDJUDtaYcrNPGn1t5RFposas0=
-github.com/resendlabs/resend-go v1.7.0/go.mod h1:yip1STH7Bqfm4fD0So5HgyNbt5taG5Cplc4xXxETyLI=
 github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
 github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
@@ -102,8 +96,6 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
 golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
 golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
 golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
 golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
 golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
 golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
-golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
-golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
 golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
 golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
 golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
 golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@@ -115,8 +107,6 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
 golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
 golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
 golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
-golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
-golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
 golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
 golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
 golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
 golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -134,8 +124,6 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
 golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
 golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
-golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=

+ 7 - 3
logic/auth.go

@@ -278,6 +278,9 @@ func UpdateUser(userchange, user *models.User) (*models.User, error) {
 		user.UserName = userchange.UserName
 		user.UserName = userchange.UserName
 	}
 	}
 	if userchange.Password != "" {
 	if userchange.Password != "" {
+		if len(userchange.Password) < 5 {
+			return &models.User{}, errors.New("password requires min 5 characters")
+		}
 		// encrypt that password so we never see it again
 		// encrypt that password so we never see it again
 		hash, err := bcrypt.GenerateFromPassword([]byte(userchange.Password), 5)
 		hash, err := bcrypt.GenerateFromPassword([]byte(userchange.Password), 5)
 
 
@@ -297,14 +300,15 @@ func UpdateUser(userchange, user *models.User) (*models.User, error) {
 	}
 	}
 	// Reset Gw Access for service users
 	// Reset Gw Access for service users
 	go UpdateUserGwAccess(*user, *userchange)
 	go UpdateUserGwAccess(*user, *userchange)
-	user.PlatformRoleID = userchange.PlatformRoleID
+	if userchange.PlatformRoleID != "" {
+		user.PlatformRoleID = userchange.PlatformRoleID
+	}
 	user.UserGroups = userchange.UserGroups
 	user.UserGroups = userchange.UserGroups
 	user.NetworkRoles = userchange.NetworkRoles
 	user.NetworkRoles = userchange.NetworkRoles
 	err := ValidateUser(user)
 	err := ValidateUser(user)
 	if err != nil {
 	if err != nil {
 		return &models.User{}, err
 		return &models.User{}, err
 	}
 	}
-
 	if err = database.DeleteRecord(database.USERS_TABLE_NAME, queryUser); err != nil {
 	if err = database.DeleteRecord(database.USERS_TABLE_NAME, queryUser); err != nil {
 		return &models.User{}, err
 		return &models.User{}, err
 	}
 	}
@@ -325,7 +329,7 @@ func ValidateUser(user *models.User) error {
 	// check if role is valid
 	// check if role is valid
 	_, err := GetRole(user.PlatformRoleID)
 	_, err := GetRole(user.PlatformRoleID)
 	if err != nil {
 	if err != nil {
-		return err
+		return errors.New("failed to fetch platform role " + user.PlatformRoleID.String())
 	}
 	}
 	v := validator.New()
 	v := validator.New()
 	_ = v.RegisterValidation("in_charset", func(fl validator.FieldLevel) bool {
 	_ = v.RegisterValidation("in_charset", func(fl validator.FieldLevel) bool {

+ 24 - 8
logic/networks.go

@@ -42,19 +42,35 @@ func SetAllocatedIpMap() error {
 		pMap := map[string]net.IP{}
 		pMap := map[string]net.IP{}
 		netName := v.NetID
 		netName := v.NetID
 
 
+		//nodes
 		nodes, err := GetNetworkNodes(netName)
 		nodes, err := GetNetworkNodes(netName)
 		if err != nil {
 		if err != nil {
 			slog.Error("could not load node for network", netName, "error", err.Error())
 			slog.Error("could not load node for network", netName, "error", err.Error())
-			continue
-		}
-
-		for _, n := range nodes {
+		} else {
+			for _, n := range nodes {
 
 
-			if n.Address.IP != nil {
-				pMap[n.Address.IP.String()] = n.Address.IP
+				if n.Address.IP != nil {
+					pMap[n.Address.IP.String()] = n.Address.IP
+				}
+				if n.Address6.IP != nil {
+					pMap[n.Address6.IP.String()] = n.Address6.IP
+				}
 			}
 			}
-			if n.Address6.IP != nil {
-				pMap[n.Address6.IP.String()] = n.Address6.IP
+
+		}
+
+		//extClients
+		extClients, err := GetNetworkExtClients(netName)
+		if err != nil {
+			slog.Error("could not load extClient for network", netName, "error", err.Error())
+		} else {
+			for _, extClient := range extClients {
+				if extClient.Address != "" {
+					pMap[extClient.Address] = net.ParseIP(extClient.Address)
+				}
+				if extClient.Address6 != "" {
+					pMap[extClient.Address6] = net.ParseIP(extClient.Address6)
+				}
 			}
 			}
 		}
 		}
 
 

+ 0 - 4
logic/security.go

@@ -6,7 +6,6 @@ import (
 	"strings"
 	"strings"
 
 
 	"github.com/gorilla/mux"
 	"github.com/gorilla/mux"
-	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
 )
 )
@@ -27,12 +26,10 @@ func SecurityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc {
 
 
 	return func(w http.ResponseWriter, r *http.Request) {
 	return func(w http.ResponseWriter, r *http.Request) {
 		r.Header.Set("ismaster", "no")
 		r.Header.Set("ismaster", "no")
-		logger.Log(0, "next", r.URL.String())
 		isGlobalAccesss := r.Header.Get("IS_GLOBAL_ACCESS") == "yes"
 		isGlobalAccesss := r.Header.Get("IS_GLOBAL_ACCESS") == "yes"
 		bearerToken := r.Header.Get("Authorization")
 		bearerToken := r.Header.Get("Authorization")
 		username, err := GetUserNameFromToken(bearerToken)
 		username, err := GetUserNameFromToken(bearerToken)
 		if err != nil {
 		if err != nil {
-			logger.Log(0, "next 1", r.URL.String(), err.Error())
 			ReturnErrorResponse(w, r, FormatError(err, "unauthorized"))
 			ReturnErrorResponse(w, r, FormatError(err, "unauthorized"))
 			return
 			return
 		}
 		}
@@ -103,7 +100,6 @@ func ContinueIfUserMatch(next http.Handler) http.HandlerFunc {
 			requestedUser, _ = url.QueryUnescape(r.URL.Query().Get("username"))
 			requestedUser, _ = url.QueryUnescape(r.URL.Query().Get("username"))
 		}
 		}
 		if requestedUser != r.Header.Get("user") {
 		if requestedUser != r.Header.Get("user") {
-			logger.Log(0, "next 2", r.URL.String(), errorResponse.Message)
 			ReturnErrorResponse(w, r, errorResponse)
 			ReturnErrorResponse(w, r, errorResponse)
 			return
 			return
 		}
 		}

+ 21 - 0
logic/user_mgmt.go

@@ -66,6 +66,27 @@ func GetRole(roleID models.UserRoleID) (models.UserRolePermissionTemplate, error
 	return ur, nil
 	return ur, nil
 }
 }
 
 
+// ListPlatformRoles - lists user platform roles permission templates
+func ListPlatformRoles() ([]models.UserRolePermissionTemplate, error) {
+	data, err := database.FetchRecords(database.USER_PERMISSIONS_TABLE_NAME)
+	if err != nil && !database.IsEmptyRecord(err) {
+		return []models.UserRolePermissionTemplate{}, err
+	}
+	userRoles := []models.UserRolePermissionTemplate{}
+	for _, dataI := range data {
+		userRole := models.UserRolePermissionTemplate{}
+		err := json.Unmarshal([]byte(dataI), &userRole)
+		if err != nil {
+			continue
+		}
+		if userRole.NetworkID != "" {
+			continue
+		}
+		userRoles = append(userRoles, userRole)
+	}
+	return userRoles, nil
+}
+
 func userRolesInit() {
 func userRolesInit() {
 	d, _ := json.Marshal(SuperAdminPermissionTemplate)
 	d, _ := json.Marshal(SuperAdminPermissionTemplate)
 	database.Insert(SuperAdminPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME)
 	database.Insert(SuperAdminPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME)

+ 11 - 10
models/user_mgmt.go

@@ -138,16 +138,17 @@ type UserGroup struct {
 
 
 // User struct - struct for Users
 // User struct - struct for Users
 type User struct {
 type User struct {
-	UserName       string                                `json:"username" bson:"username" validate:"min=3,max=40,in_charset|email"`
-	Password       string                                `json:"password" bson:"password" validate:"required,min=5"`
-	IsAdmin        bool                                  `json:"isadmin" bson:"isadmin"` // deprecated
-	IsSuperAdmin   bool                                  `json:"issuperadmin"`           // deprecated
-	RemoteGwIDs    map[string]struct{}                   `json:"remote_gw_ids"`          // deprecated
-	AuthType       AuthType                              `json:"auth_type"`
-	UserGroups     map[UserGroupID]struct{}              `json:"user_group_ids"`
-	PlatformRoleID UserRoleID                            `json:"platform_role_id"`
-	NetworkRoles   map[NetworkID]map[UserRoleID]struct{} `json:"network_roles"`
-	LastLoginTime  time.Time                             `json:"last_login_time"`
+	UserName                   string                                `json:"username" bson:"username" validate:"min=3,max=40,in_charset|email"`
+	ExternalIdentityProviderID string                                `json:"external_identity_provider_id"`
+	Password                   string                                `json:"password" bson:"password" validate:"required,min=5"`
+	IsAdmin                    bool                                  `json:"isadmin" bson:"isadmin"` // deprecated
+	IsSuperAdmin               bool                                  `json:"issuperadmin"`           // deprecated
+	RemoteGwIDs                map[string]struct{}                   `json:"remote_gw_ids"`          // deprecated
+	AuthType                   AuthType                              `json:"auth_type"`
+	UserGroups                 map[UserGroupID]struct{}              `json:"user_group_ids"`
+	PlatformRoleID             UserRoleID                            `json:"platform_role_id"`
+	NetworkRoles               map[NetworkID]map[UserRoleID]struct{} `json:"network_roles"`
+	LastLoginTime              time.Time                             `json:"last_login_time"`
 }
 }
 
 
 type ReturnUserWithRolesAndGroups struct {
 type ReturnUserWithRolesAndGroups struct {

+ 12 - 0
pro/auth/auth.go

@@ -7,6 +7,7 @@ import (
 	"strings"
 	"strings"
 	"time"
 	"time"
 
 
+	"github.com/golang-jwt/jwt/v4"
 	"github.com/gorilla/websocket"
 	"github.com/gorilla/websocket"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/logic"
@@ -236,6 +237,17 @@ func getStateAndCode(r *http.Request) (string, string) {
 	return state, code
 	return state, code
 }
 }
 
 
+func getUserEmailFromClaims(token string) string {
+	accessToken, _ := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
+		return []byte(""), nil
+	})
+	if accessToken == nil {
+		return ""
+	}
+	claims, _ := accessToken.Claims.(jwt.MapClaims)
+	return claims["email"].(string)
+}
+
 func (user *OAuthUser) getUserName() string {
 func (user *OAuthUser) getUserName() string {
 	var userName string
 	var userName string
 	if user.Email != "" {
 	if user.Email != "" {

+ 36 - 16
pro/auth/azure-ad.go

@@ -3,6 +3,7 @@ package auth
 import (
 import (
 	"context"
 	"context"
 	"encoding/json"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"fmt"
 	"io"
 	"io"
 	"net/http"
 	"net/http"
@@ -33,7 +34,7 @@ func initAzureAD(redirectURL string, clientID string, clientSecret string) {
 		RedirectURL:  redirectURL,
 		RedirectURL:  redirectURL,
 		ClientID:     clientID,
 		ClientID:     clientID,
 		ClientSecret: clientSecret,
 		ClientSecret: clientSecret,
-		Scopes:       []string{"User.Read"},
+		Scopes:       []string{"User.Read", "email", "profile", "openid"},
 		Endpoint:     microsoft.AzureADEndpoint(servercfg.GetAzureTenant()),
 		Endpoint:     microsoft.AzureADEndpoint(servercfg.GetAzureTenant()),
 	}
 	}
 }
 }
@@ -60,27 +61,37 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) {
 	var content, err = getAzureUserInfo(rState, rCode)
 	var content, err = getAzureUserInfo(rState, rCode)
 	if err != nil {
 	if err != nil {
 		logger.Log(1, "error when getting user info from azure:", err.Error())
 		logger.Log(1, "error when getting user info from azure:", err.Error())
-		if strings.Contains(err.Error(), "invalid oauth state") {
+		if strings.Contains(err.Error(), "invalid oauth state") || strings.Contains(err.Error(), "failed to fetch user email from SSO state") {
 			handleOauthNotValid(w)
 			handleOauthNotValid(w)
 			return
 			return
 		}
 		}
 		handleOauthNotConfigured(w)
 		handleOauthNotConfigured(w)
 		return
 		return
 	}
 	}
-
 	var inviteExists bool
 	var inviteExists bool
 	// check if invite exists for User
 	// check if invite exists for User
-	in, err := logic.GetUserInvite(content.UserPrincipalName)
+	in, err := logic.GetUserInvite(content.Email)
 	if err == nil {
 	if err == nil {
 		inviteExists = true
 		inviteExists = true
 	}
 	}
 	// check if user approval is already pending
 	// check if user approval is already pending
-	if !inviteExists && logic.IsPendingUser(content.UserPrincipalName) {
+	if !inviteExists && logic.IsPendingUser(content.Email) {
 		handleOauthUserSignUpApprovalPending(w)
 		handleOauthUserSignUpApprovalPending(w)
 		return
 		return
 	}
 	}
-
-	_, err = logic.GetUser(content.UserPrincipalName)
+	// if user exists with provider ID, convert them into email ID
+	user, err := logic.GetUser(content.UserPrincipalName)
+	if err == nil {
+		_, err := logic.GetUser(content.Email)
+		if err != nil {
+			user.UserName = content.Email
+			user.ExternalIdentityProviderID = content.UserPrincipalName
+			database.DeleteRecord(database.USERS_TABLE_NAME, content.UserPrincipalName)
+			d, _ := json.Marshal(user)
+			database.Insert(user.UserName, string(d), database.USERS_TABLE_NAME)
+		}
+	}
+	_, err = logic.GetUser(content.Email)
 	if err != nil {
 	if err != nil {
 		if database.IsEmptyRecord(err) { // user must not exist, so try to make one
 		if database.IsEmptyRecord(err) { // user must not exist, so try to make one
 			if inviteExists {
 			if inviteExists {
@@ -90,19 +101,20 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) {
 					logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 					logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 					return
 					return
 				}
 				}
+				user.ExternalIdentityProviderID = content.UserPrincipalName
 				if err = logic.CreateUser(&user); err != nil {
 				if err = logic.CreateUser(&user); err != nil {
 					handleSomethingWentWrong(w)
 					handleSomethingWentWrong(w)
 					return
 					return
 				}
 				}
-				logic.DeleteUserInvite(user.UserName)
-				logic.DeletePendingUser(content.UserPrincipalName)
+				logic.DeleteUserInvite(content.Email)
+				logic.DeletePendingUser(content.Email)
 			} else {
 			} else {
-				if !isEmailAllowed(content.UserPrincipalName) {
+				if !isEmailAllowed(content.Email) {
 					handleOauthUserNotAllowedToSignUp(w)
 					handleOauthUserNotAllowedToSignUp(w)
 					return
 					return
 				}
 				}
 				err = logic.InsertPendingUser(&models.User{
 				err = logic.InsertPendingUser(&models.User{
-					UserName: content.UserPrincipalName,
+					UserName: content.Email,
 				})
 				})
 				if err != nil {
 				if err != nil {
 					handleSomethingWentWrong(w)
 					handleSomethingWentWrong(w)
@@ -116,7 +128,7 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) {
 			return
 			return
 		}
 		}
 	}
 	}
-	user, err := logic.GetUser(content.UserPrincipalName)
+	user, err = logic.GetUser(content.Email)
 	if err != nil {
 	if err != nil {
 		handleOauthUserNotFound(w)
 		handleOauthUserNotFound(w)
 		return
 		return
@@ -136,7 +148,7 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) {
 	}
 	}
 	// send a netmaker jwt token
 	// send a netmaker jwt token
 	var authRequest = models.UserAuthParams{
 	var authRequest = models.UserAuthParams{
-		UserName: content.UserPrincipalName,
+		UserName: content.Email,
 		Password: newPass,
 		Password: newPass,
 	}
 	}
 
 
@@ -146,8 +158,8 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) {
 		return
 		return
 	}
 	}
 
 
-	logger.Log(1, "completed azure OAuth sigin in for", content.UserPrincipalName)
-	http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?login="+jwt+"&user="+content.UserPrincipalName, http.StatusPermanentRedirect)
+	logger.Log(1, "completed azure OAuth sigin in for", content.Email)
+	http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?login="+jwt+"&user="+content.Email, http.StatusPermanentRedirect)
 }
 }
 
 
 func getAzureUserInfo(state string, code string) (*OAuthUser, error) {
 func getAzureUserInfo(state string, code string) (*OAuthUser, error) {
@@ -166,8 +178,9 @@ func getAzureUserInfo(state string, code string) (*OAuthUser, error) {
 	}
 	}
 	var httpReq, reqErr = http.NewRequest("GET", "https://graph.microsoft.com/v1.0/me", nil)
 	var httpReq, reqErr = http.NewRequest("GET", "https://graph.microsoft.com/v1.0/me", nil)
 	if reqErr != nil {
 	if reqErr != nil {
-		return nil, fmt.Errorf("failed to create request to GitHub")
+		return nil, fmt.Errorf("failed to create request to microsoft")
 	}
 	}
+
 	httpReq.Header.Set("Authorization", "Bearer "+token.AccessToken)
 	httpReq.Header.Set("Authorization", "Bearer "+token.AccessToken)
 	response, err := http.DefaultClient.Do(httpReq)
 	response, err := http.DefaultClient.Do(httpReq)
 	if err != nil {
 	if err != nil {
@@ -183,6 +196,13 @@ func getAzureUserInfo(state string, code string) (*OAuthUser, error) {
 		return nil, fmt.Errorf("failed parsing email from response data: %s", err.Error())
 		return nil, fmt.Errorf("failed parsing email from response data: %s", err.Error())
 	}
 	}
 	userInfo.AccessToken = string(data)
 	userInfo.AccessToken = string(data)
+	if userInfo.Email == "" {
+		userInfo.Email = getUserEmailFromClaims(token.AccessToken)
+	}
+	if userInfo.Email == "" {
+		err = errors.New("failed to fetch user email from SSO state")
+		return userInfo, err
+	}
 	return userInfo, nil
 	return userInfo, nil
 }
 }
 
 

+ 1 - 1
pro/auth/error.go

@@ -18,7 +18,7 @@ var htmlBaseTemplate = `<!DOCTYPE html>
 	<script type="text/javascript">
 	<script type="text/javascript">
 	function redirect()
 	function redirect()
     {
     {
-    	window.location.href="` + servercfg.GetFrontendURL() + `";
+    	window.location.href="` + fmt.Sprintf("https://dashboard.%s/login", servercfg.GetNmBaseDomain()) + `";
     }
     }
 	</script>
 	</script>
 	<style>
 	<style>

+ 76 - 15
pro/auth/github.go

@@ -3,6 +3,7 @@ package auth
 import (
 import (
 	"context"
 	"context"
 	"encoding/json"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"fmt"
 	"io"
 	"io"
 	"net/http"
 	"net/http"
@@ -33,7 +34,7 @@ func initGithub(redirectURL string, clientID string, clientSecret string) {
 		RedirectURL:  redirectURL,
 		RedirectURL:  redirectURL,
 		ClientID:     clientID,
 		ClientID:     clientID,
 		ClientSecret: clientSecret,
 		ClientSecret: clientSecret,
-		Scopes:       []string{},
+		Scopes:       []string{"read:user", "user:email"},
 		Endpoint:     github.Endpoint,
 		Endpoint:     github.Endpoint,
 	}
 	}
 }
 }
@@ -60,26 +61,39 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) {
 	var content, err = getGithubUserInfo(rState, rCode)
 	var content, err = getGithubUserInfo(rState, rCode)
 	if err != nil {
 	if err != nil {
 		logger.Log(1, "error when getting user info from github:", err.Error())
 		logger.Log(1, "error when getting user info from github:", err.Error())
-		if strings.Contains(err.Error(), "invalid oauth state") {
+		if strings.Contains(err.Error(), "invalid oauth state") || strings.Contains(err.Error(), "failed to fetch user email from SSO state") {
 			handleOauthNotValid(w)
 			handleOauthNotValid(w)
 			return
 			return
 		}
 		}
 		handleOauthNotConfigured(w)
 		handleOauthNotConfigured(w)
 		return
 		return
 	}
 	}
-
 	var inviteExists bool
 	var inviteExists bool
 	// check if invite exists for User
 	// check if invite exists for User
-	in, err := logic.GetUserInvite(content.Login)
+	in, err := logic.GetUserInvite(content.Email)
 	if err == nil {
 	if err == nil {
 		inviteExists = true
 		inviteExists = true
 	}
 	}
 	// check if user approval is already pending
 	// check if user approval is already pending
-	if !inviteExists && logic.IsPendingUser(content.Login) {
+	if !inviteExists && logic.IsPendingUser(content.Email) {
 		handleOauthUserSignUpApprovalPending(w)
 		handleOauthUserSignUpApprovalPending(w)
 		return
 		return
 	}
 	}
-	_, err = logic.GetUser(content.Login)
+	// if user exists with provider ID, convert them into email ID
+	user, err := logic.GetUser(content.Login)
+	if err == nil {
+		// checks if user exists with email
+		_, err := logic.GetUser(content.Email)
+		if err != nil {
+			user.UserName = content.Email
+			user.ExternalIdentityProviderID = content.Login
+			database.DeleteRecord(database.USERS_TABLE_NAME, content.Login)
+			d, _ := json.Marshal(user)
+			database.Insert(user.UserName, string(d), database.USERS_TABLE_NAME)
+		}
+
+	}
+	_, err = logic.GetUser(content.Email)
 	if err != nil {
 	if err != nil {
 		if database.IsEmptyRecord(err) { // user must not exist, so try to make one
 		if database.IsEmptyRecord(err) { // user must not exist, so try to make one
 			if inviteExists {
 			if inviteExists {
@@ -89,19 +103,20 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) {
 					logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 					logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 					return
 					return
 				}
 				}
+				user.ExternalIdentityProviderID = content.Login
 				if err = logic.CreateUser(&user); err != nil {
 				if err = logic.CreateUser(&user); err != nil {
 					handleSomethingWentWrong(w)
 					handleSomethingWentWrong(w)
 					return
 					return
 				}
 				}
-				logic.DeleteUserInvite(user.UserName)
-				logic.DeletePendingUser(content.Login)
+				logic.DeleteUserInvite(content.Email)
+				logic.DeletePendingUser(content.Email)
 			} else {
 			} else {
-				if !isEmailAllowed(content.Login) {
+				if !isEmailAllowed(content.Email) {
 					handleOauthUserNotAllowedToSignUp(w)
 					handleOauthUserNotAllowedToSignUp(w)
 					return
 					return
 				}
 				}
 				err = logic.InsertPendingUser(&models.User{
 				err = logic.InsertPendingUser(&models.User{
-					UserName: content.Login,
+					UserName: content.Email,
 				})
 				})
 				if err != nil {
 				if err != nil {
 					handleSomethingWentWrong(w)
 					handleSomethingWentWrong(w)
@@ -115,7 +130,7 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) {
 			return
 			return
 		}
 		}
 	}
 	}
-	user, err := logic.GetUser(content.Login)
+	user, err = logic.GetUser(content.Email)
 	if err != nil {
 	if err != nil {
 		handleOauthUserNotFound(w)
 		handleOauthUserNotFound(w)
 		return
 		return
@@ -135,7 +150,7 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) {
 	}
 	}
 	// send a netmaker jwt token
 	// send a netmaker jwt token
 	var authRequest = models.UserAuthParams{
 	var authRequest = models.UserAuthParams{
-		UserName: content.Login,
+		UserName: content.Email,
 		Password: newPass,
 		Password: newPass,
 	}
 	}
 
 
@@ -145,11 +160,11 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) {
 		return
 		return
 	}
 	}
 
 
-	logger.Log(1, "completed github OAuth sigin in for", content.Login)
-	http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?login="+jwt+"&user="+content.Login, http.StatusPermanentRedirect)
+	logger.Log(1, "completed github OAuth sigin in for", content.Email)
+	http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?login="+jwt+"&user="+content.Email, http.StatusPermanentRedirect)
 }
 }
 
 
-func getGithubUserInfo(state string, code string) (*OAuthUser, error) {
+func getGithubUserInfo(state, code string) (*OAuthUser, error) {
 	oauth_state_string, isValid := logic.IsStateValid(state)
 	oauth_state_string, isValid := logic.IsStateValid(state)
 	if (!isValid || state != oauth_state_string) && !isStateCached(state) {
 	if (!isValid || state != oauth_state_string) && !isStateCached(state) {
 		return nil, fmt.Errorf("invalid oauth state")
 		return nil, fmt.Errorf("invalid oauth state")
@@ -186,9 +201,55 @@ func getGithubUserInfo(state string, code string) (*OAuthUser, error) {
 		return nil, fmt.Errorf("failed parsing email from response data: %s", err.Error())
 		return nil, fmt.Errorf("failed parsing email from response data: %s", err.Error())
 	}
 	}
 	userInfo.AccessToken = string(data)
 	userInfo.AccessToken = string(data)
+	if userInfo.Email == "" {
+		// if user's email is not made public, get the info from the github emails api
+		logger.Log(2, "fetching user email from github api")
+		userInfo.Email, err = getGithubEmailsInfo(token.AccessToken)
+		if err != nil {
+			logger.Log(0, "failed to fetch user's email from github: ", err.Error())
+		}
+	}
+	if userInfo.Email == "" {
+		err = errors.New("failed to fetch user email from SSO state")
+		return userInfo, err
+	}
 	return userInfo, nil
 	return userInfo, nil
 }
 }
 
 
 func verifyGithubUser(token *oauth2.Token) bool {
 func verifyGithubUser(token *oauth2.Token) bool {
 	return token.Valid()
 	return token.Valid()
 }
 }
+
+func getGithubEmailsInfo(accessToken string) (string, error) {
+
+	var httpClient = &http.Client{}
+	var httpReq, reqErr = http.NewRequest("GET", "https://api.github.com/user/emails", nil)
+	if reqErr != nil {
+		return "", fmt.Errorf("failed to create request to GitHub")
+	}
+	httpReq.Header.Add("Accept", "application/vnd.github.v3+json")
+	httpReq.Header.Set("Authorization", "token "+accessToken)
+	response, err := httpClient.Do(httpReq)
+	if err != nil {
+		return "", fmt.Errorf("failed getting user info: %s", err.Error())
+	}
+	defer response.Body.Close()
+	contents, err := io.ReadAll(response.Body)
+	if err != nil {
+		return "", fmt.Errorf("failed reading response body: %s", err.Error())
+	}
+
+	emailsInfo := []interface{}{}
+	err = json.Unmarshal(contents, &emailsInfo)
+	if err != nil {
+		return "", err
+	}
+	for _, info := range emailsInfo {
+		emailInfoMap := info.(map[string]interface{})
+		if emailInfoMap["primary"].(bool) {
+			return emailInfoMap["email"].(string), nil
+		}
+
+	}
+	return "", errors.New("email not found")
+}

+ 1 - 9
pro/auth/google.go

@@ -69,22 +69,17 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) {
 		handleOauthNotConfigured(w)
 		handleOauthNotConfigured(w)
 		return
 		return
 	}
 	}
-	logger.Log(0, "CALLBACK ----> 1")
-
-	logger.Log(0, "CALLBACK ----> 2")
 	var inviteExists bool
 	var inviteExists bool
 	// check if invite exists for User
 	// check if invite exists for User
 	in, err := logic.GetUserInvite(content.Email)
 	in, err := logic.GetUserInvite(content.Email)
 	if err == nil {
 	if err == nil {
 		inviteExists = true
 		inviteExists = true
 	}
 	}
-	logger.Log(0, fmt.Sprintf("CALLBACK ----> 3  %v", inviteExists))
 	// check if user approval is already pending
 	// check if user approval is already pending
 	if !inviteExists && logic.IsPendingUser(content.Email) {
 	if !inviteExists && logic.IsPendingUser(content.Email) {
 		handleOauthUserSignUpApprovalPending(w)
 		handleOauthUserSignUpApprovalPending(w)
 		return
 		return
 	}
 	}
-	logger.Log(0, "CALLBACK ----> 4")
 	_, err = logic.GetUser(content.Email)
 	_, err = logic.GetUser(content.Email)
 	if err != nil {
 	if err != nil {
 		if database.IsEmptyRecord(err) { // user must not exist, so try to make one
 		if database.IsEmptyRecord(err) { // user must not exist, so try to make one
@@ -95,8 +90,7 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) {
 					logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 					logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 					return
 					return
 				}
 				}
-				logger.Log(0, "CALLBACK ----> 4.0")
-
+				user.ExternalIdentityProviderID = content.Email
 				if err = logic.CreateUser(&user); err != nil {
 				if err = logic.CreateUser(&user); err != nil {
 					handleSomethingWentWrong(w)
 					handleSomethingWentWrong(w)
 					return
 					return
@@ -124,7 +118,6 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) {
 			return
 			return
 		}
 		}
 	}
 	}
-	logger.Log(0, "CALLBACK ----> 6")
 	user, err := logic.GetUser(content.Email)
 	user, err := logic.GetUser(content.Email)
 	if err != nil {
 	if err != nil {
 		logger.Log(0, "error fetching user: ", err.Error())
 		logger.Log(0, "error fetching user: ", err.Error())
@@ -186,7 +179,6 @@ func getGoogleUserInfo(state string, code string) (*OAuthUser, error) {
 	if err != nil {
 	if err != nil {
 		return nil, fmt.Errorf("failed reading response body: %s", err.Error())
 		return nil, fmt.Errorf("failed reading response body: %s", err.Error())
 	}
 	}
-	logger.Log(0, fmt.Sprintf("---------------> USERINFO: %v, token: %s", string(contents), token.AccessToken))
 	var userInfo = &OAuthUser{}
 	var userInfo = &OAuthUser{}
 	if err = json.Unmarshal(contents, userInfo); err != nil {
 	if err = json.Unmarshal(contents, userInfo); err != nil {
 		return nil, fmt.Errorf("failed parsing email from response data: %s", err.Error())
 		return nil, fmt.Errorf("failed parsing email from response data: %s", err.Error())

+ 2 - 2
pro/auth/oidc.go

@@ -80,10 +80,9 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) {
 		handleOauthNotConfigured(w)
 		handleOauthNotConfigured(w)
 		return
 		return
 	}
 	}
-
 	var inviteExists bool
 	var inviteExists bool
 	// check if invite exists for User
 	// check if invite exists for User
-	in, err := logic.GetUserInvite(content.Login)
+	in, err := logic.GetUserInvite(content.Email)
 	if err == nil {
 	if err == nil {
 		inviteExists = true
 		inviteExists = true
 	}
 	}
@@ -102,6 +101,7 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) {
 					logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 					logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 					return
 					return
 				}
 				}
+				user.ExternalIdentityProviderID = content.Email
 				if err = logic.CreateUser(&user); err != nil {
 				if err = logic.CreateUser(&user); err != nil {
 					handleSomethingWentWrong(w)
 					handleSomethingWentWrong(w)
 					return
 					return

+ 8 - 15
pro/controllers/users.go

@@ -7,6 +7,7 @@ import (
 	"fmt"
 	"fmt"
 	"net/http"
 	"net/http"
 	"net/url"
 	"net/url"
+	"strings"
 
 
 	"github.com/gorilla/mux"
 	"github.com/gorilla/mux"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
@@ -29,7 +30,6 @@ func UserHandlers(r *mux.Router) {
 	r.HandleFunc("/api/oauth/register/{regKey}", proAuth.RegisterHostSSO).Methods(http.MethodGet)
 	r.HandleFunc("/api/oauth/register/{regKey}", proAuth.RegisterHostSSO).Methods(http.MethodGet)
 
 
 	// User Role Handlers
 	// User Role Handlers
-	r.HandleFunc("/api/v1/users/roles", logic.SecurityCheck(true, http.HandlerFunc(listRoles))).Methods(http.MethodGet)
 	r.HandleFunc("/api/v1/users/role", logic.SecurityCheck(true, http.HandlerFunc(getRole))).Methods(http.MethodGet)
 	r.HandleFunc("/api/v1/users/role", logic.SecurityCheck(true, http.HandlerFunc(getRole))).Methods(http.MethodGet)
 	r.HandleFunc("/api/v1/users/role", logic.SecurityCheck(true, http.HandlerFunc(createRole))).Methods(http.MethodPost)
 	r.HandleFunc("/api/v1/users/role", logic.SecurityCheck(true, http.HandlerFunc(createRole))).Methods(http.MethodPost)
 	r.HandleFunc("/api/v1/users/role", logic.SecurityCheck(true, http.HandlerFunc(updateRole))).Methods(http.MethodPut)
 	r.HandleFunc("/api/v1/users/role", logic.SecurityCheck(true, http.HandlerFunc(updateRole))).Methods(http.MethodPut)
@@ -218,8 +218,12 @@ func inviteUsers(w http.ResponseWriter, r *http.Request) {
 			NetworkRoles:   inviteReq.NetworkRoles,
 			NetworkRoles:   inviteReq.NetworkRoles,
 			InviteCode:     logic.RandomString(8),
 			InviteCode:     logic.RandomString(8),
 		}
 		}
+		frontendURL := strings.TrimSuffix(servercfg.GetFrontendURL(), "/")
+		if frontendURL == "" {
+			frontendURL = fmt.Sprintf("https://dashboard.%s", servercfg.GetNmBaseDomain())
+		}
 		u, err := url.Parse(fmt.Sprintf("%s/invite?email=%s&invite_code=%s",
 		u, err := url.Parse(fmt.Sprintf("%s/invite?email=%s&invite_code=%s",
-			servercfg.GetFrontendURL(), url.QueryEscape(invite.Email), url.QueryEscape(invite.InviteCode)))
+			frontendURL, url.QueryEscape(invite.Email), url.QueryEscape(invite.InviteCode)))
 		if err != nil {
 		if err != nil {
 			slog.Error("failed to parse to invite url", "error", err)
 			slog.Error("failed to parse to invite url", "error", err)
 			return
 			return
@@ -502,12 +506,12 @@ func deleteUserGroup(w http.ResponseWriter, r *http.Request) {
 // @Param       role_id param string true "roleid required to get the role details"
 // @Param       role_id param string true "roleid required to get the role details"
 // @Success     200 {object}  []models.UserRolePermissionTemplate
 // @Success     200 {object}  []models.UserRolePermissionTemplate
 // @Failure     500 {object} models.ErrorResponse
 // @Failure     500 {object} models.ErrorResponse
-func listRoles(w http.ResponseWriter, r *http.Request) {
+func ListRoles(w http.ResponseWriter, r *http.Request) {
 	platform, _ := url.QueryUnescape(r.URL.Query().Get("platform"))
 	platform, _ := url.QueryUnescape(r.URL.Query().Get("platform"))
 	var roles []models.UserRolePermissionTemplate
 	var roles []models.UserRolePermissionTemplate
 	var err error
 	var err error
 	if platform == "true" {
 	if platform == "true" {
-		roles, err = proLogic.ListPlatformRoles()
+		roles, err = logic.ListPlatformRoles()
 	} else {
 	} else {
 		roles, err = proLogic.ListNetworkRoles()
 		roles, err = proLogic.ListNetworkRoles()
 	}
 	}
@@ -816,21 +820,18 @@ func removeUserFromRemoteAccessGW(w http.ResponseWriter, r *http.Request) {
 func getUserRemoteAccessGwsV1(w http.ResponseWriter, r *http.Request) {
 func getUserRemoteAccessGwsV1(w http.ResponseWriter, r *http.Request) {
 	// set header.
 	// set header.
 	w.Header().Set("Content-Type", "application/json")
 	w.Header().Set("Content-Type", "application/json")
-	logger.Log(0, "------------> 1. getUserRemoteAccessGwsV1")
 	var params = mux.Vars(r)
 	var params = mux.Vars(r)
 	username := params["username"]
 	username := params["username"]
 	if username == "" {
 	if username == "" {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("required params username"), "badrequest"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("required params username"), "badrequest"))
 		return
 		return
 	}
 	}
-	logger.Log(0, "------------> 2. getUserRemoteAccessGwsV1")
 	user, err := logic.GetUser(username)
 	user, err := logic.GetUser(username)
 	if err != nil {
 	if err != nil {
 		logger.Log(0, username, "failed to fetch user: ", err.Error())
 		logger.Log(0, username, "failed to fetch user: ", err.Error())
 		logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to fetch user %s, error: %v", username, err), "badrequest"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to fetch user %s, error: %v", username, err), "badrequest"))
 		return
 		return
 	}
 	}
-	logger.Log(0, "------------> 3. getUserRemoteAccessGwsV1")
 	remoteAccessClientID := r.URL.Query().Get("remote_access_clientid")
 	remoteAccessClientID := r.URL.Query().Get("remote_access_clientid")
 	var req models.UserRemoteGwsReq
 	var req models.UserRemoteGwsReq
 	if remoteAccessClientID == "" {
 	if remoteAccessClientID == "" {
@@ -841,7 +842,6 @@ func getUserRemoteAccessGwsV1(w http.ResponseWriter, r *http.Request) {
 			return
 			return
 		}
 		}
 	}
 	}
-	logger.Log(0, "------------> 4. getUserRemoteAccessGwsV1")
 	reqFromMobile := r.URL.Query().Get("from_mobile") == "true"
 	reqFromMobile := r.URL.Query().Get("from_mobile") == "true"
 	if req.RemoteAccessClientID == "" && remoteAccessClientID == "" {
 	if req.RemoteAccessClientID == "" && remoteAccessClientID == "" {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("remote access client id cannot be empty"), "badrequest"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("remote access client id cannot be empty"), "badrequest"))
@@ -851,15 +851,12 @@ func getUserRemoteAccessGwsV1(w http.ResponseWriter, r *http.Request) {
 		req.RemoteAccessClientID = remoteAccessClientID
 		req.RemoteAccessClientID = remoteAccessClientID
 	}
 	}
 	userGws := make(map[string][]models.UserRemoteGws)
 	userGws := make(map[string][]models.UserRemoteGws)
-	logger.Log(0, "------------> 5. getUserRemoteAccessGwsV1")
 	allextClients, err := logic.GetAllExtClients()
 	allextClients, err := logic.GetAllExtClients()
 	if err != nil {
 	if err != nil {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 		return
 	}
 	}
-	logger.Log(0, "------------> 6. getUserRemoteAccessGwsV1")
 	userGwNodes := proLogic.GetUserRAGNodes(*user)
 	userGwNodes := proLogic.GetUserRAGNodes(*user)
-	logger.Log(0, fmt.Sprintf("1. User Gw Nodes: %+v", userGwNodes))
 	for _, extClient := range allextClients {
 	for _, extClient := range allextClients {
 		node, ok := userGwNodes[extClient.IngressGatewayID]
 		node, ok := userGwNodes[extClient.IngressGatewayID]
 		if !ok {
 		if !ok {
@@ -895,10 +892,8 @@ func getUserRemoteAccessGwsV1(w http.ResponseWriter, r *http.Request) {
 			delete(userGwNodes, node.ID.String())
 			delete(userGwNodes, node.ID.String())
 		}
 		}
 	}
 	}
-	logger.Log(0, fmt.Sprintf("2. User Gw Nodes: %+v", userGwNodes))
 	// add remaining gw nodes to resp
 	// add remaining gw nodes to resp
 	for gwID := range userGwNodes {
 	for gwID := range userGwNodes {
-		logger.Log(0, "RAG ---> 1")
 		node, err := logic.GetNodeByID(gwID)
 		node, err := logic.GetNodeByID(gwID)
 		if err != nil {
 		if err != nil {
 			continue
 			continue
@@ -909,7 +904,6 @@ func getUserRemoteAccessGwsV1(w http.ResponseWriter, r *http.Request) {
 		if node.PendingDelete {
 		if node.PendingDelete {
 			continue
 			continue
 		}
 		}
-		logger.Log(0, "RAG ---> 2")
 		host, err := logic.GetHost(node.HostID.String())
 		host, err := logic.GetHost(node.HostID.String())
 		if err != nil {
 		if err != nil {
 			continue
 			continue
@@ -918,7 +912,6 @@ func getUserRemoteAccessGwsV1(w http.ResponseWriter, r *http.Request) {
 		if err != nil {
 		if err != nil {
 			slog.Error("failed to get node network", "error", err)
 			slog.Error("failed to get node network", "error", err)
 		}
 		}
-		logger.Log(0, "RAG ---> 3")
 		gws := userGws[node.Network]
 		gws := userGws[node.Network]
 
 
 		gws = append(gws, models.UserRemoteGws{
 		gws = append(gws, models.UserRemoteGws{

+ 1 - 0
pro/initialize.go

@@ -34,6 +34,7 @@ func InitPro() {
 		proControllers.FailOverHandlers,
 		proControllers.FailOverHandlers,
 		proControllers.InetHandlers,
 		proControllers.InetHandlers,
 	)
 	)
+	controller.ListRoles = proControllers.ListRoles
 	logic.EnterpriseCheckFuncs = append(logic.EnterpriseCheckFuncs, func() {
 	logic.EnterpriseCheckFuncs = append(logic.EnterpriseCheckFuncs, func() {
 		// == License Handling ==
 		// == License Handling ==
 		enableLicenseHook := false
 		enableLicenseHook := false

+ 0 - 7
pro/logic/security.go

@@ -5,7 +5,6 @@ import (
 	"fmt"
 	"fmt"
 	"net/http"
 	"net/http"
 
 
-	"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"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
@@ -37,7 +36,6 @@ func NetworkPermissionsCheck(username string, r *http.Request) error {
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	logger.Log(0, "NET MIDDL----> 1")
 	userRole, err := logic.GetRole(user.PlatformRoleID)
 	userRole, err := logic.GetRole(user.PlatformRoleID)
 	if err != nil {
 	if err != nil {
 		return errors.New("access denied")
 		return errors.New("access denied")
@@ -45,7 +43,6 @@ func NetworkPermissionsCheck(username string, r *http.Request) error {
 	if userRole.FullAccess {
 	if userRole.FullAccess {
 		return nil
 		return nil
 	}
 	}
-	logger.Log(0, "NET MIDDL----> 2")
 	// get info from header to determine the target rsrc
 	// get info from header to determine the target rsrc
 	targetRsrc := r.Header.Get("TARGET_RSRC")
 	targetRsrc := r.Header.Get("TARGET_RSRC")
 	targetRsrcID := r.Header.Get("TARGET_RSRC_ID")
 	targetRsrcID := r.Header.Get("TARGET_RSRC_ID")
@@ -102,7 +99,6 @@ func checkNetworkAccessPermissions(netRoleID models.UserRoleID, username, reqSco
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	logger.Log(0, "NET MIDDL----> 3", string(netRoleID))
 	if networkPermissionScope.FullAccess {
 	if networkPermissionScope.FullAccess {
 		return nil
 		return nil
 	}
 	}
@@ -113,7 +109,6 @@ func checkNetworkAccessPermissions(netRoleID models.UserRoleID, username, reqSco
 	if !ok {
 	if !ok {
 		return errors.New("access denied")
 		return errors.New("access denied")
 	}
 	}
-	logger.Log(0, "NET MIDDL----> 4", string(netRoleID))
 	if allRsrcsTypePermissionScope, ok := rsrcPermissionScope[models.RsrcID(fmt.Sprintf("all_%s", targetRsrc))]; ok {
 	if allRsrcsTypePermissionScope, ok := rsrcPermissionScope[models.RsrcID(fmt.Sprintf("all_%s", targetRsrc))]; ok {
 		// handle extclient apis here
 		// handle extclient apis here
 		if models.RsrcType(targetRsrc) == models.ExtClientsRsrc && allRsrcsTypePermissionScope.SelfOnly && targetRsrcID != "" {
 		if models.RsrcType(targetRsrc) == models.ExtClientsRsrc && allRsrcsTypePermissionScope.SelfOnly && targetRsrcID != "" {
@@ -139,7 +134,6 @@ func checkNetworkAccessPermissions(netRoleID models.UserRoleID, username, reqSco
 			}
 			}
 		}
 		}
 	}
 	}
-	logger.Log(0, "NET MIDDL----> 5", string(netRoleID))
 	if targetRsrcID == "" {
 	if targetRsrcID == "" {
 		return errors.New("target rsrc id is empty")
 		return errors.New("target rsrc id is empty")
 	}
 	}
@@ -149,7 +143,6 @@ func checkNetworkAccessPermissions(netRoleID models.UserRoleID, username, reqSco
 			return nil
 			return nil
 		}
 		}
 	}
 	}
-	logger.Log(0, "NET MIDDL----> 6", string(netRoleID))
 	return errors.New("access denied")
 	return errors.New("access denied")
 }
 }
 
 

+ 1 - 22
pro/logic/user_mgmt.go

@@ -201,27 +201,6 @@ func ListNetworkRoles() ([]models.UserRolePermissionTemplate, error) {
 	return userRoles, nil
 	return userRoles, nil
 }
 }
 
 
-// ListPlatformRoles - lists user platform roles permission templates
-func ListPlatformRoles() ([]models.UserRolePermissionTemplate, error) {
-	data, err := database.FetchRecords(database.USER_PERMISSIONS_TABLE_NAME)
-	if err != nil && !database.IsEmptyRecord(err) {
-		return []models.UserRolePermissionTemplate{}, err
-	}
-	userRoles := []models.UserRolePermissionTemplate{}
-	for _, dataI := range data {
-		userRole := models.UserRolePermissionTemplate{}
-		err := json.Unmarshal([]byte(dataI), &userRole)
-		if err != nil {
-			continue
-		}
-		if userRole.NetworkID != "" {
-			continue
-		}
-		userRoles = append(userRoles, userRole)
-	}
-	return userRoles, nil
-}
-
 func ValidateCreateRoleReq(userRole *models.UserRolePermissionTemplate) error {
 func ValidateCreateRoleReq(userRole *models.UserRolePermissionTemplate) error {
 	// check if role exists with this id
 	// check if role exists with this id
 	_, err := logic.GetRole(userRole.ID)
 	_, err := logic.GetRole(userRole.ID)
@@ -532,7 +511,7 @@ func HasNetworkRsrcScope(permissionTemplate models.UserRolePermissionTemplate, n
 func GetUserRAGNodes(user models.User) (gws map[string]models.Node) {
 func GetUserRAGNodes(user models.User) (gws map[string]models.Node) {
 	gws = make(map[string]models.Node)
 	gws = make(map[string]models.Node)
 	userGwAccessScope := GetUserNetworkRolesWithRemoteVPNAccess(user)
 	userGwAccessScope := GetUserNetworkRolesWithRemoteVPNAccess(user)
-	logger.Log(0, fmt.Sprintf("User Gw Access Scope: %+v", userGwAccessScope))
+	logger.Log(3, fmt.Sprintf("User Gw Access Scope: %+v", userGwAccessScope))
 	_, allNetAccess := userGwAccessScope["*"]
 	_, allNetAccess := userGwAccessScope["*"]
 	nodes, err := logic.GetAllNodes()
 	nodes, err := logic.GetAllNodes()
 	if err != nil {
 	if err != nil {

+ 5 - 6
release.md

@@ -1,15 +1,14 @@
 # Netmaker v0.25.0
 # Netmaker v0.25.0
 
 
 ## Whats New ✨
 ## Whats New ✨
-- Validation Checks For Egress Routes
-- Network Change Detection System
-- Removed Creation Of ACLs For EMQX
+- Advanced User Management with Network Roles and Groups
+- User Invitation via Email and Magic Links
 
 
 ## What's Fixed/Improved 🛠
 ## What's Fixed/Improved 🛠
-- Removed RAG Metadata Length Restriction
+
 - Scalability Improvements
 - Scalability Improvements
 - Optimised Traffic Flow Over MQ
 - Optimised Traffic Flow Over MQ
-- Improved Validation Checks For Internet GWS
+- Improved Peer Updates with Batching
 
 
 ## Known Issues 🐞
 ## Known Issues 🐞
 
 
@@ -18,5 +17,5 @@
 - IPv6 DNS Entries Are Not Working.
 - IPv6 DNS Entries Are Not Working.
 - Stale Peer On The Interface, When Forced Removed From Multiple Networks At Once.
 - Stale Peer On The Interface, When Forced Removed From Multiple Networks At Once.
 - Can Still Ping Domain Name Even When DNS Toggle Is Switched Off.
 - Can Still Ping Domain Name Even When DNS Toggle Is Switched Off.
-- WireGuard DNS issue on most flavors of Ubuntu 24.04 and some other newer Linux distributions. The issue is affecting the Remote Access Client (RAC) and the plain WireGuard external clients. Workaround can be found here https://help.netmaker.io/en/articles/9612016-extclient-rac-dns-issue-on-ubuntu-24-04.
+- WireGuard DNS issue on most flavours of Ubuntu 24.04 and some other newer Linux distributions. The issue is affecting the Remote Access Client (RAC) and the plain WireGuard external clients. Workaround can be found here https://help.netmaker.io/en/articles/9612016-extclient-rac-dns-issue-on-ubuntu-24-04.
 
 

+ 3 - 3
scripts/nm-quick.sh

@@ -129,7 +129,7 @@ setup_netclient() {
 
 
 	echo "waiting for netclient to become available"
 	echo "waiting for netclient to become available"
 	local found=false
 	local found=false
-	local file=/etc/netclient/nodes.yml
+	local file=/etc/netclient/nodes.json
 	for ((a = 1; a <= 90; a++)); do
 	for ((a = 1; a <= 90; a++)); do
 		if [ -f "$file" ]; then
 		if [ -f "$file" ]; then
 			found=true
 			found=true
@@ -147,13 +147,13 @@ setup_netclient() {
 # configure_netclient - configures server's netclient as a default host and an ingress gateway
 # configure_netclient - configures server's netclient as a default host and an ingress gateway
 configure_netclient() {
 configure_netclient() {
 	sleep 2
 	sleep 2
-	NODE_ID=$(sudo cat /etc/netclient/nodes.yml | yq -r .netmaker.commonnode.id)
+	NODE_ID=$(sudo cat /etc/netclient/nodes.json | jq -r .netmaker.id)
 	if [ "$NODE_ID" = "" ] || [ "$NODE_ID" = "null" ]; then
 	if [ "$NODE_ID" = "" ] || [ "$NODE_ID" = "null" ]; then
 		echo "Error obtaining NODE_ID for the new network"
 		echo "Error obtaining NODE_ID for the new network"
 		exit 1
 		exit 1
 	fi
 	fi
 	echo "register complete. New node ID: $NODE_ID"
 	echo "register complete. New node ID: $NODE_ID"
-	HOST_ID=$(sudo cat /etc/netclient/netclient.yml | yq -r .host.id)
+	HOST_ID=$(sudo cat /etc/netclient/netclient.json | jq -r .id)
 	if [ "$HOST_ID" = "" ] || [ "$HOST_ID" = "null" ]; then
 	if [ "$HOST_ID" = "" ] || [ "$HOST_ID" = "null" ]; then
 		echo "Error obtaining HOST_ID for the new network"
 		echo "Error obtaining HOST_ID for the new network"
 		exit 1
 		exit 1

+ 5 - 0
servercfg/serverconf.go

@@ -809,3 +809,8 @@ func GetAllowedEmailDomains() string {
 	}
 	}
 	return allowedDomains
 	return allowedDomains
 }
 }
+
+// GetNmBaseDomain - fetches nm base domain
+func GetNmBaseDomain() string {
+	return os.Getenv("NM_DOMAIN")
+}