From f9bb6cfe7dfda26f2f9200b57393a5c2d2e3805c Mon Sep 17 00:00:00 2001
From: Khanh Ngo <k@ndk.name>
Date: Tue, 2 Jun 2020 11:08:39 +0700
Subject: [PATCH] Edit client

---
 custom/js/helper.js    |  4 +--
 handler/routes.go      | 63 ++++++++++++++++++++++++++++++++--
 main.go                |  1 +
 templates/base.html    |  2 +-
 templates/clients.html | 78 ++++++++++++++++++++++++++++++++++++++++++
 util/util.go           | 14 ++++----
 6 files changed, 150 insertions(+), 12 deletions(-)

diff --git a/custom/js/helper.js b/custom/js/helper.js
index 9591026..42bc17a 100644
--- a/custom/js/helper.js
+++ b/custom/js/helper.js
@@ -9,13 +9,13 @@ function renderClientList(data) {
         // render client allocated ip addresses
         let allocatedIpsHtml = "";
         $.each(obj.Client.allocated_ips, function(index, obj) {
-            allocatedIpsHtml += `<small class="badge badge-secondary">${obj}</small>`;
+            allocatedIpsHtml += `<small class="badge badge-secondary">${obj}</small>&nbsp;`;
         })
 
         // render client allowed ip addresses
         let allowedIpsHtml = "";
         $.each(obj.Client.allowed_ips, function(index, obj) {
-            allowedIpsHtml += `<small class="badge badge-secondary">${obj}</small>`;
+            allowedIpsHtml += `<small class="badge badge-secondary">${obj}</small>&nbsp;`;
         })
 
         // render client html content
diff --git a/handler/routes.go b/handler/routes.go
index 39b3ceb..5164fd6 100644
--- a/handler/routes.go
+++ b/handler/routes.go
@@ -145,7 +145,7 @@ func NewClient() echo.HandlerFunc {
 		}
 
 		// validate the input Allocation IPs
-		allocatedIPs, err := util.GetAllocatedIPs()
+		allocatedIPs, err := util.GetAllocatedIPs("")
 		check, err := util.ValidateIPAllocation(serverInterface.Addresses, allocatedIPs, client.AllocatedIPs)
 		if !check {
 			return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, fmt.Sprintf("%s", err)})
@@ -188,6 +188,63 @@ func NewClient() echo.HandlerFunc {
 	}
 }
 
+// UpdateClient handler to update client information
+func UpdateClient() echo.HandlerFunc {
+	return func(c echo.Context) error {
+		// access validation
+		validSession(c)
+
+		_client := new(model.Client)
+		c.Bind(_client)
+
+		db, err := util.DBConn()
+		if err != nil {
+			log.Error("Cannot initialize database: ", err)
+			return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot access database"})
+		}
+
+		// validate client existence
+		client := model.Client{}
+		if err := db.Read("clients", _client.ID, &client); err != nil {
+			return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, "Client not found"})
+		}
+
+		// read server information
+		serverInterface := model.ServerInterface{}
+		if err := db.Read("server", "interfaces", &serverInterface); err != nil {
+			log.Error("Cannot fetch server interface config from database: ", err)
+			return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, fmt.Sprintf("Cannot fetch server config: %s", err)})
+		}
+
+		// validate the input Allocation IPs
+		allocatedIPs, err := util.GetAllocatedIPs(client.ID)
+		check, err := util.ValidateIPAllocation(serverInterface.Addresses, allocatedIPs, _client.AllocatedIPs)
+		if !check {
+			return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, fmt.Sprintf("%s", err)})
+		}
+
+		// validate the input AllowedIPs
+		if util.ValidateAllowedIPs(_client.AllowedIPs) == false {
+			log.Warnf("Invalid Allowed IPs input from user: %v", _client.AllowedIPs)
+			return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Allowed IPs must be in CIDR format"})
+		}
+
+		// map new data
+		client.Name = _client.Name
+		client.Email = _client.Email
+		client.Enabled = _client.Enabled
+		client.AllocatedIPs = _client.AllocatedIPs
+		client.AllowedIPs = _client.AllowedIPs
+		client.UpdatedAt = time.Now().UTC()
+
+		// write to the database
+		db.Write("clients", client.ID, &client)
+		log.Infof("Updated client information successfully => %v", client)
+
+		return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Updated client successfully"})
+	}
+}
+
 // SetClientStatus handler to enable / disable a client
 func SetClientStatus() echo.HandlerFunc {
 	return func(c echo.Context) error {
@@ -212,7 +269,7 @@ func SetClientStatus() echo.HandlerFunc {
 
 		client := model.Client{}
 		if err := db.Read("clients", clientID, &client); err != nil {
-			log.Error("Cannot fetch server interface config from database: ", err)
+			log.Error("Cannot get client from database: ", err)
 		}
 
 		client.Enabled = status
@@ -451,7 +508,7 @@ func SuggestIPAllocation() echo.HandlerFunc {
 		// we take the first available ip address from
 		// each server's network addresses.
 		suggestedIPs := make([]string, 0)
-		allocatedIPs, err := util.GetAllocatedIPs()
+		allocatedIPs, err := util.GetAllocatedIPs("")
 		if err != nil {
 			log.Error("Cannot suggest ip allocation. Failed to get list of allocated ip addresses: ", err)
 			return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot suggest ip allocation: failed to get list of allocated ip addresses"})
diff --git a/main.go b/main.go
index 95ce032..b289c99 100644
--- a/main.go
+++ b/main.go
@@ -49,6 +49,7 @@ func main() {
 	app.POST("/login", handler.Login())
 	app.GET("/logout", handler.Logout())
 	app.POST("/new-client", handler.NewClient())
+	app.POST("/update-client", handler.UpdateClient())
 	app.POST("/client/set-status", handler.SetClientStatus())
 	app.POST("/remove-client", handler.RemoveClient())
 	app.GET("/download", handler.DownloadClient())
diff --git a/templates/base.html b/templates/base.html
index 681bb45..0e04f31 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -377,7 +377,7 @@
                     },
                     client_email: {
                         required: "Please enter an email address",
-                        email: "Please enter a vaild email address"
+                        email: "Please enter a valid email address"
                     },
                 },
                 errorElement: 'span',
diff --git a/templates/clients.html b/templates/clients.html
index 33fd92f..689d5d2 100644
--- a/templates/clients.html
+++ b/templates/clients.html
@@ -41,6 +41,7 @@ Wireguard Clients
             </div>
             <form name="frm_edit_client" id="frm_edit_client">
                 <div class="modal-body">
+                    <input type="hidden" id="_client_id" name="_client_id">
                     <div class="form-group">
                         <label for="_client_name" class="control-label">Name</label>
                         <input type="text" class="form-control" id="_client_name" name="_client_name">
@@ -276,6 +277,7 @@ Wireguard Clients
                         const client = resp.Client;
 
                         modal.find(".modal-title").text("Edit Client " + client.name);
+                        modal.find("#_client_id").val(client.id);
                         modal.find("#_client_name").val(client.name);
                         modal.find("#_client_email").val(client.email);
 
@@ -298,5 +300,81 @@ Wireguard Clients
                 });
             });
         });
+
+        // submitEditClient function for updating an existing client
+        function submitEditClient() {
+            const client_id = $("#_client_id").val();
+            const name = $("#_client_name").val();
+            const email = $("#_client_email").val();
+            const allocated_ips = $("#_client_allocated_ips").val().split(",");
+            const allowed_ips = $("#_client_allowed_ips").val().split(",");
+            let enabled = false;
+
+            if ($("#_enabled").is(':checked')){
+                enabled = true;
+            }
+
+            const data = {"id": client_id, "name": name, "email": email, "allocated_ips": allocated_ips,
+                "allowed_ips": allowed_ips, "enabled": enabled};
+
+            $.ajax({
+                cache: false,
+                method: 'POST',
+                url: '/update-client',
+                dataType: 'json',
+                contentType: "application/json",
+                data: JSON.stringify(data),
+                success: function(resp) {
+                    $("#modal_edit_client").modal('hide');
+                    toastr.success('Updated client successfully');
+                     // Refresh the home page (clients page) after updating successfully
+                    location.reload();
+                },
+                error: function(jqXHR, exception) {
+                    const responseJson = jQuery.parseJSON(jqXHR.responseText);
+                    toastr.error(responseJson['message']);
+                }
+            });
+        }
+
+        // Edit client form validation
+        $(document).ready(function () {
+            $.validator.setDefaults({
+                submitHandler: function () {
+                    submitEditClient();
+                }
+            });
+            $("#frm_edit_client").validate({
+                rules: {
+                    client_name: {
+                        required: true,
+                    },
+                    client_email: {
+                        required: true,
+                        email: true,
+                    },
+                },
+                messages: {
+                    client_name: {
+                        required: "Please enter a name"
+                    },
+                    client_email: {
+                        required: "Please enter an email address",
+                        email: "Please enter a valid email address"
+                    },
+                },
+                errorElement: 'span',
+                errorPlacement: function (error, element) {
+                    error.addClass('invalid-feedback');
+                    element.closest('.form-group').append(error);
+                },
+                highlight: function (element, errorClass, validClass) {
+                    $(element).addClass('is-invalid');
+                },
+                unhighlight: function (element, errorClass, validClass) {
+                    $(element).removeClass('is-invalid');
+                }
+            });
+        });
     </script>
 {{end}}
\ No newline at end of file
diff --git a/util/util.go b/util/util.go
index 384886f..d98aaec 100644
--- a/util/util.go
+++ b/util/util.go
@@ -173,7 +173,7 @@ func GetIPFromCIDR(cidr string) (string, error) {
 }
 
 // GetAllocatedIPs to get all ip addresses allocated to clients and server
-func GetAllocatedIPs() ([]string, error) {
+func GetAllocatedIPs(ignoreClientID string) ([]string, error) {
 	allocatedIPs := make([]string, 0)
 
 	// initialize database directory
@@ -211,12 +211,14 @@ func GetAllocatedIPs() ([]string, error) {
 			return nil, err
 		}
 
-		for _, cidr := range client.AllocatedIPs {
-			ip, err := GetIPFromCIDR(cidr)
-			if err != nil {
-				return nil, err
+		if client.ID != ignoreClientID {
+			for _, cidr := range client.AllocatedIPs {
+				ip, err := GetIPFromCIDR(cidr)
+				if err != nil {
+					return nil, err
+				}
+				allocatedIPs = append(allocatedIPs, ip)
 			}
-			allocatedIPs = append(allocatedIPs, ip)
 		}
 	}