Browse Source

changed all error responses

afeiszli 4 years ago
parent
commit
cc81830516

BIN
controllers/.nodeHttpController.go.swp


+ 162 - 149
controllers/groupHttpController.go

@@ -3,6 +3,7 @@ package controller
 import (
     "gopkg.in/go-playground/validator.v9"
     "github.com/gravitl/netmaker/models"
+    "errors"
     "encoding/base64"
     "github.com/gravitl/netmaker/functions"
     "github.com/gravitl/netmaker/mongoconn"
@@ -21,11 +22,10 @@ import (
 func groupHandlers(r *mux.Router) {
     r.HandleFunc("/api/groups", securityCheck(http.HandlerFunc(getGroups))).Methods("GET")
     r.HandleFunc("/api/groups", securityCheck(http.HandlerFunc(createGroup))).Methods("POST")
-    r.HandleFunc("/api/groups/{groupname}/keyupdate", securityCheck(http.HandlerFunc(keyUpdate))).Methods("POST")
     r.HandleFunc("/api/groups/{groupname}", securityCheck(http.HandlerFunc(getGroup))).Methods("GET")
-    r.HandleFunc("/api/groups/{groupname}/numnodes", securityCheck(http.HandlerFunc(getGroupNodeNumber))).Methods("GET")
     r.HandleFunc("/api/groups/{groupname}", securityCheck(http.HandlerFunc(updateGroup))).Methods("PUT")
     r.HandleFunc("/api/groups/{groupname}", securityCheck(http.HandlerFunc(deleteGroup))).Methods("DELETE")
+    r.HandleFunc("/api/groups/{groupname}/keyupdate", securityCheck(http.HandlerFunc(keyUpdate))).Methods("POST")
     r.HandleFunc("/api/groups/{groupname}/keys", securityCheck(http.HandlerFunc(createAccessKey))).Methods("POST")
     r.HandleFunc("/api/groups/{groupname}/keys", securityCheck(http.HandlerFunc(getAccessKeys))).Methods("GET")
     r.HandleFunc("/api/groups/{groupname}/keys/{name}", securityCheck(http.HandlerFunc(deleteAccessKey))).Methods("DELETE")
@@ -42,12 +42,16 @@ func securityCheck(next http.Handler) http.HandlerFunc {
 
 		var params = mux.Vars(r)
 		hasgroup := params["groupname"] != ""
-		groupexists, _ := functions.GroupExists(params["groupname"])
-                if hasgroup && !groupexists {
+		groupexists, err := functions.GroupExists(params["groupname"])
+                if err != nil {
+			returnErrorResponse(w, r, formatError(err, "internal"))
+			return
+		} else if hasgroup && !groupexists {
                         errorResponse = models.ErrorResponse{
                                 Code: http.StatusNotFound, Message: "W1R3: This group does not exist.",
                         }
                         returnErrorResponse(w, r, errorResponse)
+			return
                 } else {
 
 		bearerToken := r.Header.Get("Authorization")
@@ -68,6 +72,7 @@ func securityCheck(next http.Handler) http.HandlerFunc {
 				Code: http.StatusUnauthorized, Message: "W1R3: You are unauthorized to access this endpoint.",
 			}
 			returnErrorResponse(w, r, errorResponse)
+			return
 		} else {
 			next.ServeHTTP(w, r)
 		}
@@ -85,15 +90,16 @@ func authenticateMaster(tokenString string) bool {
 //simple get all groups function
 func getGroups(w http.ResponseWriter, r *http.Request) {
 
-	//depends on list groups function
-	//TODO: This is perhaps a more efficient way of handling ALL http handlers
-	//Take their primary logic and put in a separate function
-	//May be better since most http handler functionality is needed internally cross-method
-	//E.G. a method may need to check against all groups. But it  cant call this function. That's why there's ListGroups
-	groups := functions.ListGroups()
-
-	json.NewEncoder(w).Encode(groups)
+	groups, err := functions.ListGroups()
 
+	if err != nil {
+		returnErrorResponse(w, r, formatError(err, "internal"))
+		return
+	} else {
+		w.WriteHeader(http.StatusOK)
+		json.NewEncoder(w).Encode(groups)
+		return
+	}
 }
 
 func validateGroup(operation string, group models.Group) error {
@@ -105,14 +111,23 @@ func validateGroup(operation string, group models.Group) error {
                 return isvalid
         })
 
+        _ = v.RegisterValidation("privaterange_valid", func(fl validator.FieldLevel) bool {
+                isvalid := !*group.IsPrivate || functions.IsIpv4CIDR(fl.Field().String())
+                return isvalid
+        })
+
         _ = v.RegisterValidation("nameid_valid", func(fl validator.FieldLevel) bool {
-		isFieldUnique := operation == "update" || functions.IsGroupNameUnique(fl.Field().String())
-		inGroupCharSet := functions.NameInGroupCharSet(fl.Field().String())
-		return isFieldUnique && inGroupCharSet
+		isFieldUnique := false
+		inCharSet := false
+		if operation == "update" { isFieldUnique = true } else{
+			isFieldUnique, _ = functions.IsGroupNameUnique(fl.Field().String())
+			inCharSet        = functions.NameInGroupCharSet(fl.Field().String())
+		}
+		return isFieldUnique && inCharSet
         })
 
         _ = v.RegisterValidation("displayname_unique", func(fl validator.FieldLevel) bool {
-                isFieldUnique := functions.IsGroupDisplayNameUnique(fl.Field().String())
+                isFieldUnique, _ := functions.IsGroupDisplayNameUnique(fl.Field().String())
                 return isFieldUnique ||  operation == "update"
         })
 
@@ -126,47 +141,6 @@ func validateGroup(operation string, group models.Group) error {
         return err
 }
 
-//Get number of nodes associated with a group
-//May not be necessary, but I think the front end needs it? This should be reviewed after iteration 1
-func getGroupNodeNumber(w http.ResponseWriter, r *http.Request) {
-
-        var params = mux.Vars(r)
-
-	count, err := GetGroupNodeNumber(params["groupname"])
-
-        if err != nil {
-		var errorResponse = models.ErrorResponse{
-			Code: http.StatusInternalServerError, Message: "W1R3: Error retrieving nodes.",
-		}
-		returnErrorResponse(w, r, errorResponse)
-	} else  {
-	json.NewEncoder(w).Encode(count)
-	}
-}
-
-//This is haphazard
-//I need a better folder structure
-//maybe a functions/ folder and then a node.go, group.go, keys.go, misc.go
-func GetGroupNodeNumber(groupName string) (int,  error){
-
-        collection := mongoconn.Client.Database("netmaker").Collection("nodes")
-
-        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
-
-        filter := bson.M{"group": groupName}
-        count, err := collection.CountDocuments(ctx, filter)
-	returncount := int(count)
-
-	//not sure if this is the right way of handling this error...
-        if err != nil {
-                return 9999, err
-        }
-
-        defer cancel()
-
-        return returncount, err
-}
-
 //Simple get group function
 func getGroup(w http.ResponseWriter, r *http.Request) {
 
@@ -187,10 +161,10 @@ func getGroup(w http.ResponseWriter, r *http.Request) {
         defer cancel()
 
         if err != nil {
-                mongoconn.GetError(err, w)
+		returnErrorResponse(w,r,formatError(err, "internal"))
                 return
         }
-
+	w.WriteHeader(http.StatusOK)
         json.NewEncoder(w).Encode(group)
 }
 
@@ -204,8 +178,10 @@ func keyUpdate(w http.ResponseWriter, r *http.Request) {
 
         group, err := functions.GetParentGroup(params["groupname"])
         if err != nil {
-		return
-        }
+                returnErrorResponse(w,r,formatError(err, "internal"))
+                return
+	}
+
 
 	group.KeyUpdateTimeStamp = time.Now().Unix()
 
@@ -234,16 +210,16 @@ func keyUpdate(w http.ResponseWriter, r *http.Request) {
                 }},
         }
 
-        errN := collection.FindOneAndUpdate(ctx, filter, update).Decode(&group)
+        err = collection.FindOneAndUpdate(ctx, filter, update).Decode(&group)
 
         defer cancel()
 
-        if errN != nil {
-                mongoconn.GetError(errN, w)
-                fmt.Println(errN)
+        if err != nil {
+                returnErrorResponse(w,r,formatError(err, "internal"))
                 return
         }
 
+        w.WriteHeader(http.StatusOK)
         json.NewEncoder(w).Encode(group)
 }
 
@@ -258,6 +234,7 @@ func updateGroup(w http.ResponseWriter, r *http.Request) {
 
 	group, err := functions.GetParentGroup(params["groupname"])
         if err != nil {
+                returnErrorResponse(w,r,formatError(err, "internal"))
                 return
         }
 
@@ -265,6 +242,7 @@ func updateGroup(w http.ResponseWriter, r *http.Request) {
 
 	haschange := false
 	hasrangeupdate := false
+	hasprivaterangeupdate := false
 
 	_ = json.NewDecoder(r.Body).Decode(&groupChange)
 
@@ -278,16 +256,12 @@ func updateGroup(w http.ResponseWriter, r *http.Request) {
 
         err = validateGroup("update", groupChange)
         if err != nil {
+		returnErrorResponse(w,r,formatError(err, "internal"))
                 return
         }
 
-
-	//TODO: group.Name is  not update-able
-	//group.Name acts as  the ID for the group and keeps it unique and searchable by nodes
-	//should consider renaming to group.ID  
-	//Too lazy for now.
-	//DisplayName is the editable version and will not be used for node searches,
-	//but will be used by front end.
+	//NOTE: Group.NameID is intentionally NOT editable. It acts as a static ID for the group. 
+	//DisplayName can be changed instead, which is what shows on the front end
 
         if groupChange.AddressRange != "" {
 
@@ -295,55 +269,64 @@ func updateGroup(w http.ResponseWriter, r *http.Request) {
 
 	    var isAddressOK bool = functions.IsIpv4CIDR(groupChange.AddressRange)
             if !isAddressOK {
+		    err := errors.New("Invalid Range of " +  groupChange.AddressRange + " for addresses.")
+		    returnErrorResponse(w,r,formatError(err, "internal"))
                     return
             }
              haschange = true
 	     hasrangeupdate = true
 
         }
+	if groupChange.PrivateRange != "" {
+            group.PrivateRange = groupChange.PrivateRange
 
-       if groupChange.DefaultListenPort != 0 {
-            group.DefaultListenPort = groupChange.DefaultListenPort
+            var isAddressOK bool = functions.IsIpv4CIDR(groupChange.PrivateRange)
+            if !isAddressOK {
+		    err := errors.New("Invalid Range of " +  groupChange.PrivateRange + " for internal addresses.")
+                    returnErrorResponse(w,r,formatError(err, "internal"))
+                    return
+            }
              haschange = true
+             hasprivaterangeupdate = true
+	}
+	if groupChange.IsPrivate != nil {
+		group.IsPrivate = groupChange.IsPrivate
+	}
+	if groupChange.DefaultListenPort != 0 {
+		group.DefaultListenPort = groupChange.DefaultListenPort
+		haschange = true
         }
         if groupChange.DefaultPreUp != "" {
-            group.DefaultPreUp = groupChange.DefaultPreUp
-             haschange = true
+		group.DefaultPreUp = groupChange.DefaultPreUp
+		haschange = true
         }
         if groupChange.DefaultInterface != "" {
-            group.DefaultInterface = groupChange.DefaultInterface
-             haschange = true
+		group.DefaultInterface = groupChange.DefaultInterface
+		haschange = true
         }
         if groupChange.DefaultPostUp != "" {
-            group.DefaultPostUp = groupChange.DefaultPostUp
-             haschange = true
+		group.DefaultPostUp = groupChange.DefaultPostUp
+		haschange = true
         }
         if groupChange.DefaultKeepalive != 0 {
-            group.DefaultKeepalive = groupChange.DefaultKeepalive
-             haschange = true
+		group.DefaultKeepalive = groupChange.DefaultKeepalive
+		haschange = true
         }
         if groupChange.DisplayName != "" {
-            group.DisplayName = groupChange.DisplayName
-             haschange = true
+		group.DisplayName = groupChange.DisplayName
+		haschange = true
         }
         if groupChange.DefaultCheckInInterval != 0 {
-            group.DefaultCheckInInterval = groupChange.DefaultCheckInInterval
-             haschange = true
+		group.DefaultCheckInInterval = groupChange.DefaultCheckInInterval
+		haschange = true
         }
-
-	//TODO: Important. This doesn't work. This will create cases where we will
-	//unintentionally go from allowing manual signup to disallowing
-	//need to find a smarter way
-	//maybe make into a text field
-        if groupChange.AllowManualSignUp != group.AllowManualSignUp {
-            group.AllowManualSignUp = groupChange.AllowManualSignUp
-             haschange = true
+        if groupChange.AllowManualSignUp != nil {
+		group.AllowManualSignUp = groupChange.AllowManualSignUp
+		haschange = true
         }
 
         collection := mongoconn.Client.Database("netmaker").Collection("groups")
-
         ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
-
         filter := bson.M{"nameid": params["groupname"]}
 
 	if haschange {
@@ -364,27 +347,44 @@ func updateGroup(w http.ResponseWriter, r *http.Request) {
                         {"nodeslastmodified", group.NodesLastModified},
                         {"grouplastmodified", group.GroupLastModified},
                         {"allowmanualsignup", group.AllowManualSignUp},
+                        {"privaterange", group.PrivateRange},
+                        {"isprivate", group.IsPrivate},
                         {"defaultcheckininterval", group.DefaultCheckInInterval},
 		}},
         }
 
-	errN := collection.FindOneAndUpdate(ctx, filter, update).Decode(&group)
-
+	err = collection.FindOneAndUpdate(ctx, filter, update).Decode(&group)
         defer cancel()
 
-        if errN != nil {
-                mongoconn.GetError(errN, w)
-		fmt.Println(errN)
+        if err != nil {
+                returnErrorResponse(w,r,formatError(err, "internal"))
                 return
         }
 
 	//Cycles through nodes and gives them new IP's based on the new range
 	//Pretty cool, but also pretty inefficient currently
         if hasrangeupdate {
-		_ = functions.UpdateGroupNodeAddresses(params["groupname"])
-		//json.NewEncoder(w).Encode(errG)
+		err = functions.UpdateGroupNodeAddresses(params["groupname"])
+		if err != nil {
+			returnErrorResponse(w,r,formatError(err, "internal"))
+			return
+		}
 	}
-        json.NewEncoder(w).Encode(group)
+	if hasprivaterangeupdate {
+                err = functions.UpdateGroupPrivateAddresses(params["groupname"])
+                if err != nil {
+                        returnErrorResponse(w,r,formatError(err, "internal"))
+                        return
+                }
+	}
+	returngroup, err := functions.GetParentGroup(group.NameID)
+        if err != nil {
+                returnErrorResponse(w,r,formatError(err, "internal"))
+                return
+        }
+
+        w.WriteHeader(http.StatusOK)
+        json.NewEncoder(w).Encode(returngroup)
 }
 
 //Delete a group
@@ -395,15 +395,12 @@ func deleteGroup(w http.ResponseWriter, r *http.Request) {
 
         var params = mux.Vars(r)
 
-        var errorResponse = models.ErrorResponse{
-                Code: http.StatusInternalServerError, Message: "W1R3: It's not you it's me.",
-        }
-
-	nodecount, err := GetGroupNodeNumber(params["groupname"])
-
-	//we dont wanna leave nodes hanging. They need a group!
-        if nodecount > 0 || err != nil {
-                errorResponse = models.ErrorResponse{
+	nodecount, err := functions.GetGroupNodeNumber(params["groupname"])
+	if err != nil {
+		returnErrorResponse(w, r, formatError(err, "internal"))
+		return
+	} else if nodecount > 0  {
+		errorResponse := models.ErrorResponse{
                         Code: http.StatusForbidden, Message: "W1R3: Node check failed. All nodes must be deleted before deleting group.",
                 }
                 returnErrorResponse(w, r, errorResponse)
@@ -421,12 +418,12 @@ func deleteGroup(w http.ResponseWriter, r *http.Request) {
         defer cancel()
 
         if err != nil {
-                mongoconn.GetError(err, w)
+                returnErrorResponse(w,r,formatError(err, "internal"))
                 return
         }
 
+        w.WriteHeader(http.StatusOK)
         json.NewEncoder(w).Encode(deleteResult)
-
 }
 
 //Create a group
@@ -435,51 +432,50 @@ func createGroup(w http.ResponseWriter, r *http.Request) {
 
         w.Header().Set("Content-Type", "application/json")
 
-	//TODO: 
-	//This may be needed to get error response. May be why some errors dont work
-	//analyze different error responses and see what needs to be done
-	//commenting out for now
-	/*
-        var errorResponse = models.ErrorResponse{
-                Code: http.StatusInternalServerError, Message: "W1R3: It's not you it's me.",
-        }
-	*/
         var group models.Group
 
         // we decode our body request params
-        _ = json.NewDecoder(r.Body).Decode(&group)
+	err := json.NewDecoder(r.Body).Decode(&group)
+        if err != nil {
+                returnErrorResponse(w,r,formatError(err, "internal"))
+                return
+        }
 
 	//TODO: Not really doing good validation here. Same as createNode, updateNode, and updateGroup
 	//Need to implement some better validation across the board
-        err := validateGroup("create", group)
+        if group.IsPrivate == nil {
+                falsevar := false
+                group.IsPrivate = &falsevar
+        }
+
+        err = validateGroup("create", group)
         if err != nil {
+                returnErrorResponse(w,r,formatError(err, "internal"))
                 return
         }
-
 	group.SetDefaults()
         group.SetNodesLastModified()
         group.SetGroupLastModified()
         group.KeyUpdateTimeStamp = time.Now().Unix()
 
-
         collection := mongoconn.Client.Database("netmaker").Collection("groups")
         ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
 
 
         // insert our group into the group table
         result, err := collection.InsertOne(ctx, group)
-        _ = result
 
         defer cancel()
 
         if err != nil {
-                mongoconn.GetError(err, w)
+                returnErrorResponse(w,r,formatError(err, "internal"))
                 return
         }
+        w.WriteHeader(http.StatusOK)
+        json.NewEncoder(w).Encode(result)
 }
 
 // BEGIN KEY MANAGEMENT SECTION
-// Consider a separate file for these controllers but I think same file is fine for now
 
 
 //TODO: Very little error handling
@@ -496,10 +492,15 @@ func createAccessKey(w http.ResponseWriter, r *http.Request) {
         //start here
 	group, err := functions.GetParentGroup(params["groupname"])
         if err != nil {
+                returnErrorResponse(w,r,formatError(err, "internal"))
                 return
         }
 
-        _ = json.NewDecoder(r.Body).Decode(&accesskey)
+        err = json.NewDecoder(r.Body).Decode(&accesskey)
+        if err != nil {
+                returnErrorResponse(w,r,formatError(err, "internal"))
+                return
+        }
 
 	if accesskey.Name == "" {
                 accesskey.Name = functions.GenKeyName()
@@ -510,20 +511,18 @@ func createAccessKey(w http.ResponseWriter, r *http.Request) {
         if accesskey.Uses == 0 {
                 accesskey.Uses = 1
         }
-	gconf, errG := functions.GetGlobalConfig()
-        if errG != nil {
-                mongoconn.GetError(errG, w)
+	gconf, err := functions.GetGlobalConfig()
+        if err != nil {
+                returnErrorResponse(w,r,formatError(err, "internal"))
                 return
         }
 
-
 	network := params["groupname"]
 	address := gconf.ServerGRPC + gconf.PortGRPC
 
 	accessstringdec := address + "." + network + "." + accesskey.Value
 	accesskey.AccessString = base64.StdEncoding.EncodeToString([]byte(accessstringdec))
 
-
 	group.AccessKeys = append(group.AccessKeys, accesskey)
 
         collection := mongoconn.Client.Database("netmaker").Collection("groups")
@@ -543,15 +542,17 @@ func createAccessKey(w http.ResponseWriter, r *http.Request) {
                 }},
         }
 
-        errN := collection.FindOneAndUpdate(ctx, filter, update).Decode(&group)
+        err = collection.FindOneAndUpdate(ctx, filter, update).Decode(&group)
 
         defer cancel()
 
-        if errN != nil {
-                mongoconn.GetError(errN, w)
+	if err != nil {
+                returnErrorResponse(w,r,formatError(err, "internal"))
                 return
         }
-	w.Write([]byte(accesskey.AccessString))
+        w.WriteHeader(http.StatusOK)
+        json.NewEncoder(w).Encode(accesskey)
+	//w.Write([]byte(accesskey.AccessString))
 }
 
 //pretty simple get
@@ -575,18 +576,19 @@ func getAccessKeys(w http.ResponseWriter, r *http.Request) {
         defer cancel()
 
         if err != nil {
-                mongoconn.GetError(err, w)
+                returnErrorResponse(w,r,formatError(err, "internal"))
                 return
         }
-	keydata, keyerr := json.Marshal(group.AccessKeys)
+	keydata, err := json.Marshal(group.AccessKeys)
 
-        if keyerr != nil {
+        if err != nil {
+                returnErrorResponse(w,r,formatError(err, "internal"))
                 return
         }
 
 	json.Unmarshal(keydata, &keys)
 
-        //json.NewEncoder(w).Encode(group.AccessKeys)
+	w.WriteHeader(http.StatusOK)
         json.NewEncoder(w).Encode(keys)
 }
 
@@ -604,9 +606,9 @@ func deleteAccessKey(w http.ResponseWriter, r *http.Request) {
         //start here
 	group, err := functions.GetParentGroup(params["groupname"])
         if err != nil {
+                returnErrorResponse(w,r,formatError(err, "internal"))
                 return
         }
-
 	//basically, turn the list of access keys into the list of access keys before and after the item
 	//have not done any error handling for if there's like...1 item. I think it works? need to test.
 	for i := len(group.AccessKeys) - 1; i >= 0; i-- {
@@ -632,12 +634,23 @@ func deleteAccessKey(w http.ResponseWriter, r *http.Request) {
                 }},
         }
 
-        errN := collection.FindOneAndUpdate(ctx, filter, update).Decode(&group)
+        err = collection.FindOneAndUpdate(ctx, filter, update).Decode(&group)
 
         defer cancel()
 
-        if errN != nil {
-                mongoconn.GetError(errN, w)
+        if err != nil {
+                returnErrorResponse(w,r,formatError(err, "internal"))
                 return
         }
+        var keys []models.AccessKey
+	keydata, err := json.Marshal(group.AccessKeys)
+        if err != nil {
+                returnErrorResponse(w,r,formatError(err, "internal"))
+                return
+        }
+
+        json.Unmarshal(keydata, &keys)
+
+        w.WriteHeader(http.StatusOK)
+        json.NewEncoder(w).Encode(keys)
 }

+ 139 - 142
controllers/nodeHttpController.go

@@ -2,6 +2,7 @@ package controller
 
 import (
     "github.com/gravitl/netmaker/models"
+    "errors"
     "github.com/gravitl/netmaker/functions"
     "github.com/gravitl/netmaker/mongoconn"
     "golang.org/x/crypto/bcrypt"
@@ -10,7 +11,6 @@ import (
     "fmt"
     "context"
     "encoding/json"
-    "log"
     "net/http"
     "github.com/gorilla/mux"
     "go.mongodb.org/mongo-driver/bson"
@@ -20,17 +20,18 @@ import (
 
 func nodeHandlers(r *mux.Router) {
 
-    r.HandleFunc("/api/{group}/nodes", authorize(true, "group", http.HandlerFunc(getGroupNodes))).Methods("GET")
     r.HandleFunc("/api/nodes", authorize(false, "master", http.HandlerFunc(getAllNodes))).Methods("GET")
-    r.HandleFunc("/api/{group}/peerlist", authorize(true, "group", http.HandlerFunc(getPeerList))).Methods("GET")
-    r.HandleFunc("/api/{group}/lastmodified", authorize(true, "group", http.HandlerFunc(getLastModified))).Methods("GET")
-    r.HandleFunc("/api/{group}/nodes/{macaddress}", authorize(true, "node", http.HandlerFunc(getNode))).Methods("GET")
-    r.HandleFunc("/api/{group}/nodes", createNode).Methods("POST")
-    r.HandleFunc("/api/{group}/nodes/{macaddress}", authorize(true, "node", http.HandlerFunc(updateNode))).Methods("PUT")
-    r.HandleFunc("/api/{group}/nodes/{macaddress}/checkin", authorize(true, "node", http.HandlerFunc(checkIn))).Methods("POST")
-    r.HandleFunc("/api/{group}/nodes/{macaddress}/uncordon", authorize(true, "master", http.HandlerFunc(uncordonNode))).Methods("POST")
-    r.HandleFunc("/api/{group}/nodes/{macaddress}", authorize(true, "node", http.HandlerFunc(deleteNode))).Methods("DELETE")
-    r.HandleFunc("/api/{group}/authenticate", authenticate).Methods("POST")
+    r.HandleFunc("/api/nodes/{group}", authorize(true, "group", http.HandlerFunc(getGroupNodes))).Methods("GET")
+    r.HandleFunc("/api/nodes/{group}/{macaddress}", authorize(true, "node", http.HandlerFunc(getNode))).Methods("GET")
+    r.HandleFunc("/api/nodes/{group}/{macaddress}", authorize(true, "node", http.HandlerFunc(updateNode))).Methods("PUT")
+    r.HandleFunc("/api/nodes/{group}/{macaddress}", authorize(true, "node", http.HandlerFunc(deleteNode))).Methods("DELETE")
+    r.HandleFunc("/api/nodes/{group}/{macaddress}/checkin", authorize(true, "node", http.HandlerFunc(checkIn))).Methods("POST")
+//    r.HandleFunc("/api/nodes/{group}/{macaddress}/creategateway", authorize(true, "master", http.HandlerFunc(createGateway))).Methods("POST")
+//    r.HandleFunc("/api/nodes/{group}/{macaddress}/deletegateway", authorize(true, "master", http.HandlerFunc(deleteGateway))).Methods("POST")
+    r.HandleFunc("/api/nodes/{group}/{macaddress}/uncordon", authorize(true, "master", http.HandlerFunc(uncordonNode))).Methods("POST")
+    r.HandleFunc("/api/nodes/{group}/nodes", createNode).Methods("POST")
+    r.HandleFunc("/api/nodes/adm/{group}/lastmodified", authorize(true, "group", http.HandlerFunc(getLastModified))).Methods("GET")
+    r.HandleFunc("/api/nodes/adm/{group}/authenticate", authenticate).Methods("POST")
 
 }
 
@@ -109,6 +110,7 @@ func authenticate(response http.ResponseWriter, request *http.Request) {
                     returnErrorResponse(response, request, errorResponse)
 		    return
                 }
+	        response.WriteHeader(http.StatusOK)
                 response.Header().Set("Content-Type", "application/json")
                 response.Write(successJSONResponse)
             }
@@ -227,62 +229,6 @@ func authorize(groupCheck bool, authGroup string, next http.Handler) http.Handle
 	}
 }
 
-//Returns a list of peers in "plaintext" format, which can be piped straight to a file (peers.conf) on a local machine
-//Not sure if it would be better to do that here or to let the client handle the formatting.
-//TODO: May want to consider a different approach
-func getPeerList(w http.ResponseWriter, r *http.Request) {
-        w.Header().Set("Content-Type", "application/json")
-
-        var nodes []models.Node
-	var params = mux.Vars(r)
-
-        //Connection mongoDB with mongoconn class
-        collection := mongoconn.Client.Database("netmaker").Collection("nodes")
-
-        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
-
-	//Get all nodes in the relevant group which are NOT in pending state
-        filter := bson.M{"group": params["group"], "ispending": false}
-        cur, err := collection.Find(ctx, filter)
-
-        if err != nil {
-                mongoconn.GetError(err, w)
-                return
-        }
-
-        // Close the cursor once finished and cancel if it takes too long
-	defer cancel()
-
-        for cur.Next(context.TODO()) {
-
-                var node models.Node
-                err := cur.Decode(&node)
-                if err != nil {
-                        log.Fatal(err)
-                }
-
-                // add the node to our node array
-		//maybe better to just return this? But then that's just GetNodes...
-                nodes = append(nodes, node)
-        }
-
-	//Uh oh, fatal error! This needs some better error handling
-	//TODO: needs appropriate error handling so the server doesnt shut down.
-        if err := cur.Err(); err != nil {
-                log.Fatal(err)
-        }
-
-	//Writes output in the style familiar to WireGuard
-	//Get's piped to peers.conf locally after client request
-	for _, n := range nodes {
-		w.Write([]byte("[Peer] \n"))
-	        w.Write([]byte("PublicKey = " + n.PublicKey + "\n"))
-	        w.Write([]byte("AllowedIPs = " + n.Address + "/32" + "\n"))
-	        w.Write([]byte("PersistentKeepalive = " + fmt.Sprint(n.PersistentKeepalive) + "\n"))
-		w.Write([]byte("Endpoint = " + n.Endpoint + ":" + fmt.Sprint(n.ListenPort) + "\n\n"))
-	}
-}
-
 //Gets all nodes associated with group, including pending nodes
 func getGroupNodes(w http.ResponseWriter, r *http.Request) {
 
@@ -300,10 +246,10 @@ func getGroupNodes(w http.ResponseWriter, r *http.Request) {
 	//Filtering out the ID field cuz Dillon doesn't like it. May want to filter out other fields in the future
 	cur, err := collection.Find(ctx, filter, options.Find().SetProjection(bson.M{"_id": 0}))
 
-	if err != nil {
-		mongoconn.GetError(err, w)
-		return
-	}
+        if err != nil {
+                returnErrorResponse(w,r,formatError(err, "internal"))
+                return
+        }
 
 	defer cancel()
 
@@ -317,8 +263,9 @@ func getGroupNodes(w http.ResponseWriter, r *http.Request) {
 		var node models.ReturnNode
 
 		err := cur.Decode(&node)
-		if err != nil {
-			log.Fatal(err)
+	        if err != nil {
+			returnErrorResponse(w,r,formatError(err, "internal"))
+			return
 		}
 
 		// add item our array of nodes
@@ -327,10 +274,12 @@ func getGroupNodes(w http.ResponseWriter, r *http.Request) {
 
 	//TODO: Another fatal error we should take care of.
 	if err := cur.Err(); err != nil {
-		log.Fatal(err)
+                returnErrorResponse(w,r,formatError(err, "internal"))
+                return
 	}
 
 	//Returns all the nodes in JSON format
+        w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(nodes)
 
 }
@@ -349,9 +298,8 @@ func getAllNodes(w http.ResponseWriter, r *http.Request) {
 
 	// Filter out them ID's again
 	cur, err := collection.Find(ctx, bson.M{}, options.Find().SetProjection(bson.M{"_id": 0}))
-
         if err != nil {
-                mongoconn.GetError(err, w)
+                returnErrorResponse(w,r,formatError(err, "internal"))
                 return
         }
 
@@ -361,22 +309,23 @@ func getAllNodes(w http.ResponseWriter, r *http.Request) {
 
                 var node models.ReturnNode
                 err := cur.Decode(&node)
-
-		//TODO: Fatal error
-                if err != nil {
-                        log.Fatal(err)
-                }
-
+	        if err != nil {
+		        returnErrorResponse(w,r,formatError(err, "internal"))
+			return
+		}
                 // add node to our array
                 nodes = append(nodes, node)
         }
 
 	//TODO: Fatal error
         if err := cur.Err(); err != nil {
-                log.Fatal(err)
+                returnErrorResponse(w,r,formatError(err, "internal"))
+                return
         }
+
 	//Return all the nodes in JSON format
-        json.NewEncoder(w).Encode(nodes)
+        w.WriteHeader(http.StatusOK)
+	json.NewEncoder(w).Encode(nodes)
 
 }
 
@@ -422,16 +371,18 @@ func checkIn(w http.ResponseWriter, r *http.Request) {
                 }},
         }
 
-        errN := collection.FindOneAndUpdate(ctx, filter, update).Decode(&node)
+        err := collection.FindOneAndUpdate(ctx, filter, update).Decode(&node)
 
         defer cancel()
 
-        if errN != nil {
-                mongoconn.GetError(errN, w)
+        if err != nil {
+                returnErrorResponse(w,r,formatError(err, "internal"))
                 return
         }
+
 	//TODO: check node last modified vs group last modified
-        json.NewEncoder(w).Encode(node)
+        w.WriteHeader(http.StatusOK)
+	json.NewEncoder(w).Encode(node)
 
 }
 
@@ -443,12 +394,11 @@ func getNode(w http.ResponseWriter, r *http.Request) {
         var params = mux.Vars(r)
 
 	node, err := GetNode(params["macaddress"], params["group"])
-
-	if err != nil {
-		mongoconn.GetError(err, w)
-		return
-	}
-
+        if err != nil {
+                returnErrorResponse(w,r,formatError(err, "internal"))
+                return
+        }
+	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(node)
 }
 
@@ -472,11 +422,12 @@ func getLastModified(w http.ResponseWriter, r *http.Request) {
 
 	defer cancel()
 
-	if err != nil {
-		fmt.Println(err)
-		//log.Fatal(err)
-	}
+        if err != nil {
+                returnErrorResponse(w,r,formatError(err, "internal"))
+                return
+        }
 
+	w.WriteHeader(http.StatusOK)
 	w.Write([]byte(string(group.NodesLastModified)))
 
 }
@@ -498,10 +449,12 @@ func createNode(w http.ResponseWriter, r *http.Request) {
 	//Check if group exists  first
 	//TODO: This is inefficient. Let's find a better way. 
 	//Just a few rows down we grab the group anyway
-        groupexists, errgroup := functions.GroupExists(groupName)
+        groupexists, err := functions.GroupExists(groupName)
 
-
-        if !groupexists || errgroup != nil {
+        if err != nil {
+                returnErrorResponse(w,r,formatError(err, "internal"))
+                return
+        } else if !groupexists {
                 errorResponse = models.ErrorResponse{
                         Code: http.StatusNotFound, Message: "W1R3: Group does not exist! ",
                 }
@@ -512,12 +465,20 @@ func createNode(w http.ResponseWriter, r *http.Request) {
 	var node models.Node
 
 	//get node from body of request
-	_ = json.NewDecoder(r.Body).Decode(&node)
+	err = json.NewDecoder(r.Body).Decode(&node)
+        if err != nil {
+                returnErrorResponse(w,r,formatError(err, "internal"))
+                return
+        }
 
 	node.Group = groupName
 
 
-	group, _ := node.GetGroup()
+	group, err := node.GetGroup()
+        if err != nil {
+                returnErrorResponse(w,r,formatError(err, "internal"))
+                return
+        }
 
 	//Check to see if key is valid
 	//TODO: Triple inefficient!!! This is the third call to the DB we make for groups
@@ -537,16 +498,18 @@ func createNode(w http.ResponseWriter, r *http.Request) {
 		}
 	}
 
-	err :=  ValidateNode("create", groupName, node)
+	err =  ValidateNode("create", groupName, node)
         if err != nil {
-		return
+                returnErrorResponse(w,r,formatError(err, "internal"))
+                return
         }
 
         node, err = CreateNode(node, groupName)
         if err != nil {
+                returnErrorResponse(w,r,formatError(err, "internal"))
                 return
         }
-
+	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(node)
 }
 
@@ -561,8 +524,8 @@ func uncordonNode(w http.ResponseWriter, r *http.Request) {
 
 	node, err := functions.GetNodeByMacAddress(params["group"], params["macaddress"])
         if err != nil {
-                mongoconn.GetError(err, w)
-		return
+                returnErrorResponse(w,r,formatError(err, "internal"))
+                return
         }
 
         collection := mongoconn.Client.Database("netmaker").Collection("nodes")
@@ -583,21 +546,71 @@ func uncordonNode(w http.ResponseWriter, r *http.Request) {
                 }},
         }
 
-        errN := collection.FindOneAndUpdate(ctx, filter, update).Decode(&node)
+        err = collection.FindOneAndUpdate(ctx, filter, update).Decode(&node)
 
         defer cancel()
 
-        if errN != nil {
-                mongoconn.GetError(errN, w)
+	if err != nil {
+                returnErrorResponse(w,r,formatError(err, "internal"))
                 return
         }
+
         fmt.Println("Node " + node.Name + " uncordoned.")
+	w.WriteHeader(http.StatusOK)
+        json.NewEncoder(w).Encode("SUCCESS")
+}
+
+func createGateway(w http.ResponseWriter, r *http.Request) {
+        w.Header().Set("Content-Type", "application/json")
+
+        var params = mux.Vars(r)
+
+        var node models.Node
+
+        node, err := functions.GetNodeByMacAddress(params["group"], params["macaddress"])
+        if err != nil {
+                returnErrorResponse(w,r,formatError(err, "internal"))
+                return
+        }
+
+        collection := mongoconn.Client.Database("netmaker").Collection("nodes")
+
+        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+
+        // Create filter
+        filter := bson.M{"macaddress": params["macaddress"], "group": params["group"]}
+
+        node.SetLastModified()
+
+        err =  ValidateNode("create", params["group"], node)
+        if err != nil {
+                returnErrorResponse(w,r,formatError(err, "internal"))
+                return
+        }
 
+        // prepare update model.
+        update := bson.D{
+                {"$set", bson.D{
+                        {"ispending", false},
+                }},
+        }
 
+        err = collection.FindOneAndUpdate(ctx, filter, update).Decode(&node)
+
+        defer cancel()
+
+        if err != nil {
+                returnErrorResponse(w,r,formatError(err, "internal"))
+                return
+        }
+        fmt.Println("Node " + node.Name + " uncordoned.")
+
+	w.WriteHeader(http.StatusOK)
         json.NewEncoder(w).Encode("SUCCESS")
 }
 
 
+
 func updateNode(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
 
@@ -611,11 +624,12 @@ func updateNode(w http.ResponseWriter, r *http.Request) {
 	//start here
 	node, err := functions.GetNodeByMacAddress(params["group"], params["macaddress"])
         if err != nil {
-                json.NewEncoder(w).Encode(err)
+                returnErrorResponse(w,r,formatError(err, "internal"))
                 return
         }
 
 
+
         var nodechange models.Node
 
         // we decode our body request params
@@ -628,19 +642,17 @@ func updateNode(w http.ResponseWriter, r *http.Request) {
 	}
 
         err = ValidateNode("update", params["group"], nodechange)
-
         if err != nil {
-                json.NewEncoder(w).Encode(err)
+                returnErrorResponse(w,r,formatError(err, "internal"))
                 return
         }
 
 	node, err = UpdateNode(nodechange, node)
-
-	if err != nil {
-                json.NewEncoder(w).Encode(err)
-		return
-	}
-
+        if err != nil {
+                returnErrorResponse(w,r,formatError(err, "internal"))
+                return
+        }
+	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(node)
 }
 
@@ -655,29 +667,14 @@ func deleteNode(w http.ResponseWriter, r *http.Request) {
 
 	success, err := DeleteNode(params["macaddress"], params["group"])
 
-	if err != nil || !success {
-		json.NewEncoder(w).Encode("Could not delete node " + params["macaddress"])
+	if err != nil {
+                returnErrorResponse(w,r,formatError(err, "internal"))
+                return
+        } else if !success {
+		err = errors.New("Could not delete node " + params["macaddress"])
+                returnErrorResponse(w,r,formatError(err, "internal"))
 		return
 	}
-
+	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(params["macaddress"] + " deleted.")
 }
-
-//A fun lil method for handling errors with http
-//Used in some cases but not others
-//1. This should probably be an application-wide function
-//2. All the API calls should probably be using this
-//3. The mongoconn should probably use this.
-//4. Need a consistent approach to error handling generally. Very haphazard at the moment
-//TODO: This is important. All Handlers  should be replying with appropriate error code.
-func returnErrorResponse(response http.ResponseWriter, request *http.Request, errorMesage models.ErrorResponse) {
-        httpResponse := &models.ErrorResponse{Code: errorMesage.Code, Message: errorMesage.Message}
-        jsonResponse, err := json.Marshal(httpResponse)
-        if err != nil {
-                panic(err)
-        }
-        response.Header().Set("Content-Type", "application/json")
-        response.WriteHeader(errorMesage.Code)
-        response.Write(jsonResponse)
-}
-

+ 49 - 0
controllers/responseHttp.go

@@ -0,0 +1,49 @@
+package controller
+
+import (
+    "github.com/gravitl/netmaker/models"
+    "encoding/json"
+    "net/http"
+    "fmt"
+)
+
+func formatError(err error, errType string) models.ErrorResponse {
+
+	var status = http.StatusInternalServerError
+	switch errType {
+	case "internal":
+		status = http.StatusInternalServerError
+	case "badrequest":
+		status  = http.StatusBadRequest
+	case "notfound":
+		status = http.StatusNotFound
+	case "unauthorized":
+		status = http.StatusUnauthorized
+	case "forbidden":
+		status = http.StatusForbidden
+	default:
+		status = http.StatusInternalServerError
+	}
+
+        var response = models.ErrorResponse{
+                Message: err.Error(),
+                Code: status,
+        }
+	return response
+}
+
+func returnSuccessResponse(response http.ResponseWriter, request *http.Request, errorMesage models.ErrorResponse) {
+
+}
+
+func returnErrorResponse(response http.ResponseWriter, request *http.Request, errorMessage models.ErrorResponse) {
+        httpResponse := &models.ErrorResponse{Code: errorMessage.Code, Message: errorMessage.Message}
+        jsonResponse, err := json.Marshal(httpResponse)
+        if err != nil {
+                panic(err)
+        }
+	fmt.Println(errorMessage)
+        response.Header().Set("Content-Type", "application/json")
+        response.WriteHeader(errorMessage.Code)
+        response.Write(jsonResponse)
+}

+ 6 - 6
controllers/userHttpController.go

@@ -21,12 +21,12 @@ import (
 
 func userHandlers(r *mux.Router) {
 
-    r.HandleFunc("/users/hasadmin", hasAdmin).Methods("GET")
-    r.HandleFunc("/users/createadmin", createAdmin).Methods("POST")
-    r.HandleFunc("/users/{username}", authorizeUser(http.HandlerFunc(updateUser))).Methods("PUT")
-    r.HandleFunc("/users/{username}", authorizeUser(http.HandlerFunc(deleteUser))).Methods("DELETE")
-    r.HandleFunc("/users/{username}", authorizeUser(http.HandlerFunc(getUser))).Methods("GET")
-    r.HandleFunc("/users/authenticate", authenticateUser).Methods("POST")
+    r.HandleFunc("/api/users/adm/hasadmin", hasAdmin).Methods("GET")
+    r.HandleFunc("/api/users/adm/createadmin", createAdmin).Methods("POST")
+    r.HandleFunc("/api/users/adm/authenticate", authenticateUser).Methods("POST")
+    r.HandleFunc("/api/users/{username}", authorizeUser(http.HandlerFunc(updateUser))).Methods("PUT")
+    r.HandleFunc("/api/users/{username}", authorizeUser(http.HandlerFunc(deleteUser))).Methods("DELETE")
+    r.HandleFunc("/api/users/{username}", authorizeUser(http.HandlerFunc(getUser))).Methods("GET")
 }
 
 //Node authenticates using its password and retrieves a JWT for authorization.

+ 86 - 12
functions/helpers.go

@@ -12,7 +12,6 @@ import (
     "context"
     "encoding/base64"
     "strings"
-    "log"
     "net"
     "github.com/gravitl/netmaker/models"
     "github.com/gravitl/netmaker/mongoconn"
@@ -171,13 +170,62 @@ func UpdateGroupNodeAddresses(groupName string) error {
 
         return err
 }
+//TODO TODO TODO!!!!!
+func UpdateGroupPrivateAddresses(groupName string) error {
+
+        //Connection mongoDB with mongoconn class
+        collection := mongoconn.Client.Database("netmaker").Collection("nodes")
+
+        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+
+        filter := bson.M{"group": groupName}
+        cur, err := collection.Find(ctx, filter)
+
+        if err != nil {
+                return err
+        }
+
+        defer cancel()
+
+        for cur.Next(context.TODO()) {
+
+                var node models.Node
+
+                err := cur.Decode(&node)
+                if err != nil {
+                        fmt.Println("error in node address assignment!")
+                        return err
+                }
+                ipaddr, iperr := UniqueAddress(groupName)
+                if iperr != nil {
+                        fmt.Println("error in node  address assignment!")
+                        return iperr
+                }
+
+                filter := bson.M{"macaddress": node.MacAddress}
+                update := bson.D{{"$set", bson.D{{"address", ipaddr}}}}
+
+                errN := collection.FindOneAndUpdate(ctx, filter, update).Decode(&node)
+
+                defer cancel()
+                if errN != nil {
+                        return errN
+                }
+        }
+
+        return err
+}
 
 //Checks to see if any other groups have the same name (id)
-func IsGroupNameUnique(name string) bool {
+func IsGroupNameUnique(name string) (bool, error ){
 
         isunique := true
 
-        dbs := ListGroups()
+        dbs, err := ListGroups()
+
+	if err != nil {
+		return false, err
+	}
 
 	for i := 0; i < len(dbs); i++ {
 
@@ -186,14 +234,18 @@ func IsGroupNameUnique(name string) bool {
 		}
 	}
 
-        return isunique
+        return isunique, nil
 }
 
-func IsGroupDisplayNameUnique(name string) bool {
+func IsGroupDisplayNameUnique(name string) (bool, error){
 
         isunique := true
 
-        dbs := ListGroups()
+        dbs, err := ListGroups()
+        if err != nil {
+                return false, err
+        }
+
 
         for i := 0; i < len(dbs); i++ {
 
@@ -202,12 +254,34 @@ func IsGroupDisplayNameUnique(name string) bool {
                 }
         }
 
-        return isunique
+        return isunique, nil
+}
+
+func GetGroupNodeNumber(groupName string) (int,  error){
+
+        collection := mongoconn.Client.Database("wirecat").Collection("nodes")
+
+        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+
+        filter := bson.M{"group": groupName}
+        count, err := collection.CountDocuments(ctx, filter)
+	returncount := int(count)
+
+	//not sure if this is the right way of handling this error...
+        if err != nil {
+                return 9999, err
+        }
+
+        defer cancel()
+
+        return returncount, err
 }
 
+
 //Kind  of a weird name. Should just be GetGroups I think. Consider changing.
 //Anyway, returns all the groups
-func ListGroups() []models.Group{
+func ListGroups() ([]models.Group, error){
+
         var groups []models.Group
 
         collection := mongoconn.Client.Database("netmaker").Collection("groups")
@@ -217,7 +291,7 @@ func ListGroups() []models.Group{
         cur, err := collection.Find(ctx, bson.M{}, options.Find().SetProjection(bson.M{"_id": 0}))
 
         if err != nil {
-                return groups
+                return groups, err
         }
 
         defer cancel()
@@ -227,7 +301,7 @@ func ListGroups() []models.Group{
                 var group models.Group
                 err := cur.Decode(&group)
                 if err != nil {
-                        log.Fatal(err)
+			return groups, err
                 }
 
                 // add group our array
@@ -235,10 +309,10 @@ func ListGroups() []models.Group{
         }
 
         if err := cur.Err(); err != nil {
-                log.Fatal(err)
+                return groups, err
         }
 
-        return groups
+        return groups, err
 }
 
 //Checks to see if access key is valid

+ 2 - 0
models/group.go

@@ -24,6 +24,8 @@ type Group struct {
         DefaultSaveConfig      *bool `json:"defaultsaveconfig" bson:"defaultsaveconfig"`
 	AccessKeys	[]AccessKey `json:"accesskeys" bson:"accesskeys"`
 	AllowManualSignUp *bool `json:"allowmanualsignup" bson:"allowmanualsignup"`
+	IsPrivate *bool `json:"isprivate" bson:"isprivate"`
+	PrivateRange string `json:"privaterange" bson:"privaterange" validate:"privaterange_valid"`
 	DefaultCheckInInterval int32 `json:"checkininterval,omitempty" bson:"checkininterval,omitempty" validate:"omitempty,numeric,min=1,max=100000"`
 }
 

+ 7 - 0
models/structs.go

@@ -99,3 +99,10 @@ type PeersResponse struct {
     KeepAlive int32 `json:"persistentkeepalive" bson:"persistentkeepalive"`
 }
 
+type GatewayRequest struct {
+    RangeString string `json:"rangestring" bson:"rangestring"`
+    Ranges []string `json:"ranges" bson:"ranges"`
+    Interface string `json:"interface" bson:"interface"`
+}
+
+

+ 33 - 15
netclient/functions/common.go

@@ -78,7 +78,9 @@ func Install(accesskey string, password string, server string, group string, noa
 	tserver := ""
 	tnetwork := ""
 	tkey := ""
-
+	trange := ""
+	var localrange *net.IPNet
+	islocal := false
 	if FileExists("/etc/systemd/system/netclient-"+group+".timer") ||
 	   FileExists("/etc/netclient/netconfig-"+group) {
 		   err := errors.New("ALREADY_INSTALLED. Netclient appears to already be installed for network " + group + ". To re-install, please remove by executing 'sudo netclient -c remove -n " + group + "'. Then re-run the install command.")
@@ -95,13 +97,29 @@ func Install(accesskey string, password string, server string, group string, noa
 		tserver = tokenvals[0]
 		tnetwork = tokenvals[1]
 		tkey = tokenvals[2]
-		server = tserver
-		group = tnetwork
-		accesskey = tkey
+		trange = tokenvals[3]
+		if server == "" {
+			server = tserver
+		}
+		if group == "" {
+			group = tnetwork
+		}
+		if accesskey == "" {
+			accesskey = tkey
+		}
+		if trange != "" {
+			islocal = true
+			_, localrange, err = net.ParseCIDR(trange)
+
+		} else {
+			trange = "Not a local network. Will use public address for endpoint."
+		}
+
 		fmt.Println("Decoded values from token:")
 		fmt.Println("    Server: " + tserver)
 		fmt.Println("    Network: " + tnetwork)
 		fmt.Println("    Key: " + tkey)
+		fmt.Println("    Local Range: " + localrange.String())
 	}
         wgclient, err := wgctrl.New()
 
@@ -125,9 +143,6 @@ func Install(accesskey string, password string, server string, group string, noa
                         server = servercfg.Address
 		}
 	}
-	if tserver != "" {
-		server = tserver
-	}
        fmt.Println("     Server: " + server)
 
 	if accesskey == "" {
@@ -137,9 +152,6 @@ func Install(accesskey string, password string, server string, group string, noa
 			accesskey = servercfg.AccessKey
 		}
 	}
-	if tkey != "" {
-		accesskey = tkey
-	}
        fmt.Println("     AccessKey: " + accesskey)
        err = config.WriteServer(server, accesskey, group)
         if err != nil {
@@ -167,9 +179,6 @@ func Install(accesskey string, password string, server string, group string, noa
 			group = nodecfg.Group
 		}
         }
-	if tnetwork != "" {
-		group =  tnetwork
-	}
        fmt.Println("     Group: " + group)
 
 	var macaddress string
@@ -218,13 +227,22 @@ func Install(accesskey string, password string, server string, group string, noa
 					if !found {
 						ip = v.IP
 						local = ip.String()
-						found = true
+						if islocal {
+							found = localrange.Contains(ip)
+						} else {
+							found = true
+						}
 					}
 				case *net.IPAddr:
 					if  !found {
 						ip = v.IP
 						local = ip.String()
-						found = true
+						if islocal {
+							found = localrange.Contains(ip)
+
+						} else {
+							found = true
+						}
 					}
 				}
 			}