From febf075f8d874ed2f844193517be8ae539715ea7 Mon Sep 17 00:00:00 2001
From: Khanh Ngo <k@ndk.name>
Date: Sun, 19 Apr 2020 15:50:59 +0700
Subject: [PATCH] Add Server config page Handle server ip addresses input and
 store TODO: Key pair form

---
 client/khanh.json                     |   4 -
 handler/routes.go                     |  59 ++++++++-
 main.go                               |   5 +-
 model/server.go                       |  25 ++++
 router/router.go                      |   3 +-
 templates/base.html                   |  18 ++-
 templates/{home.html => clients.html} |   6 +-
 templates/server.html                 | 165 ++++++++++++++++++++++++++
 util/util.go                          |  22 +++-
 9 files changed, 285 insertions(+), 22 deletions(-)
 delete mode 100644 client/khanh.json
 create mode 100644 model/server.go
 rename templates/{home.html => clients.html} (98%)
 create mode 100644 templates/server.html

diff --git a/client/khanh.json b/client/khanh.json
deleted file mode 100644
index ef5f7c7..0000000
--- a/client/khanh.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-	"privateKey": "xyz",
-	"pulbicKey": "123"
-}
\ No newline at end of file
diff --git a/handler/routes.go b/handler/routes.go
index 959a131..03a51b4 100644
--- a/handler/routes.go
+++ b/handler/routes.go
@@ -16,8 +16,8 @@ import (
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 )
 
-// Home handler
-func Home() echo.HandlerFunc {
+// WireGuardClients handler
+func WireGuardClients() echo.HandlerFunc {
 	return func(c echo.Context) error {
 		// initialize database directory
 		dir := "./db"
@@ -53,7 +53,7 @@ func Home() echo.HandlerFunc {
 			clientDataList = append(clientDataList, clientData)
 		}
 
-		return c.Render(http.StatusOK, "home.html", map[string]interface{}{
+		return c.Render(http.StatusOK, "clients.html", map[string]interface{}{
 			"name":           "Khanh",
 			"clientDataList": clientDataList,
 		})
@@ -68,7 +68,7 @@ func NewClient() echo.HandlerFunc {
 
 		// validate the input AllowedIPs
 		if util.ValidateAllowedIPs(client.AllowedIPs) == false {
-			log.Warn("Invalid Allowed IPs input from user: %v", client.AllowedIPs)
+			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"})
 		}
 
@@ -122,3 +122,54 @@ func RemoveClient() echo.HandlerFunc {
 		return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Client removed"})
 	}
 }
+
+// WireGuardServer handler
+func WireGuardServer() echo.HandlerFunc {
+	return func(c echo.Context) error {
+
+		// initialize database directory
+		dir := "./db"
+		db, err := scribble.New(dir, nil)
+		if err != nil {
+			log.Error("Cannot initialize the database: ", err)
+		}
+
+		serverInterface := model.ServerInterface{}
+		if err := db.Read("server", "interfaces", &serverInterface); err != nil {
+			log.Error("Cannot fetch server interface config from database: ", err)
+		}
+
+		return c.Render(http.StatusOK, "server.html", map[string]interface{}{
+			"name":            "Khanh",
+			"serverInterface": serverInterface,
+		})
+	}
+}
+
+// WireGuardServerInterfaces handler
+func WireGuardServerInterfaces() echo.HandlerFunc {
+	return func(c echo.Context) error {
+		serverInterface := new(model.ServerInterface)
+		c.Bind(serverInterface)
+
+		// validate the input addresses
+		if util.ValidateServerAddresses(serverInterface.Addresses) == false {
+			log.Warnf("Invalid server interface addresses input from user: %v", serverInterface.Addresses)
+			return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Interface IP addresses must be in CIDR format"})
+		}
+
+		serverInterface.UpdatedAt = time.Now().UTC()
+
+		// write config to the database
+		dir := "./db"
+		db, err := scribble.New(dir, nil)
+		if err != nil {
+			log.Error("Cannot initialize the database: ", err)
+			return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot access database"})
+		}
+		db.Write("server", "interfaces", serverInterface)
+		log.Infof("Updated wireguard server interfaces settings: %v", serverInterface)
+
+		return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Updated interface addresses successfully"})
+	}
+}
diff --git a/main.go b/main.go
index 95d1d20..3334bdb 100644
--- a/main.go
+++ b/main.go
@@ -8,9 +8,10 @@ import (
 func main() {
 	app := router.New()
 
-	app.GET("/", handler.Home())
+	app.GET("/", handler.WireGuardClients())
 	app.POST("/new-client", handler.NewClient())
 	app.POST("/remove-client", handler.RemoveClient())
-
+	app.GET("/wg-server", handler.WireGuardServer())
+	app.POST("wg-server/interfaces", handler.WireGuardServerInterfaces())
 	app.Logger.Fatal(app.Start("127.0.0.1:5000"))
 }
diff --git a/model/server.go b/model/server.go
new file mode 100644
index 0000000..06ab21a
--- /dev/null
+++ b/model/server.go
@@ -0,0 +1,25 @@
+package model
+
+import (
+	"time"
+)
+
+// Server model
+type Server struct {
+	KeyPair   *ServerKeypair
+	Interface *ServerInterface
+}
+
+// ServerKeypair model
+type ServerKeypair struct {
+	PrivateKey string    `json:"private_key"`
+	PublicKey  string    `json:"pulbic_key"`
+	UpdatedAt  time.Time `json:"updated_at"`
+}
+
+// ServerInterface model
+type ServerInterface struct {
+	Addresses  []string  `json:"addresses"`
+	ListenPort int       `json:"listen_port,string"` // ,string to get listen_port string input as int
+	UpdatedAt  time.Time `json:"updated_at"`
+}
diff --git a/router/router.go b/router/router.go
index d30d710..c2d6bcc 100644
--- a/router/router.go
+++ b/router/router.go
@@ -29,7 +29,8 @@ func (t *TemplateRegistry) Render(w io.Writer, name string, data interface{}, c
 func New() *echo.Echo {
 	e := echo.New()
 	templates := make(map[string]*template.Template)
-	templates["home.html"] = template.Must(template.ParseFiles("templates/home.html", "templates/base.html"))
+	templates["clients.html"] = template.Must(template.ParseFiles("templates/clients.html", "templates/base.html"))
+	templates["server.html"] = template.Must(template.ParseFiles("templates/server.html", "templates/base.html"))
 
 	e.Logger.SetLevel(log.DEBUG)
 	e.Pre(middleware.RemoveTrailingSlash())
diff --git a/templates/base.html b/templates/base.html
index 834a04b..14ef39c 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -86,18 +86,26 @@
                 <nav class="mt-2">
                     <ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="menu" data-accordion="false">
                         <li class="nav-item">
-                            <a href="#" class="nav-link active">
-                                <i class="nav-icon fas fa-th"></i>
+                            <a href="/" class="nav-link active">
+                                <i class="nav-icon fas fa-user-secret"></i>
                                 <p>
-                                    Dashboard
+                                    Wireguard Clients
                                 </p>
                             </a>
                         </li>
                         <li class="nav-item">
-                            <a href="#" class="nav-link">
+                            <a href="/wg-server" class="nav-link">
                                 <i class="nav-icon fas fa-server"></i>
                                 <p>
-                                    Server Config
+                                    Wireguard Server
+                                </p>
+                            </a>
+                        </li>
+                        <li class="nav-item">
+                            <a href="/golbal-settings" class="nav-link">
+                                <i class="nav-icon fas fa-cog"></i>
+                                <p>
+                                    Global Settings
                                 </p>
                             </a>
                         </li>
diff --git a/templates/home.html b/templates/clients.html
similarity index 98%
rename from templates/home.html
rename to templates/clients.html
index 9a65140..7347ce2 100644
--- a/templates/home.html
+++ b/templates/clients.html
@@ -1,5 +1,5 @@
 {{define "title"}}
-Home
+Wireguard Clients
 {{end}}
 
 {{define "username"}}
@@ -7,13 +7,13 @@ Home
 {{end}}
 
 {{define "page_title"}}
-Dashboard
+Wireguard Clients
 {{end}}
 
 {{define "page_content"}}
 <section class="content">
     <div class="container-fluid">
-        <h5 class="mt-4 mb-2">Wireguard Clients</h5>
+        <!-- <h5 class="mt-4 mb-2">Wireguard Clients</h5> -->
         <div class="row">
             {{range .clientDataList}}
             <div class="col-sm-6">
diff --git a/templates/server.html b/templates/server.html
new file mode 100644
index 0000000..3e6c938
--- /dev/null
+++ b/templates/server.html
@@ -0,0 +1,165 @@
+{{define "title"}}
+Wireguard Server
+{{end}}
+
+{{define "username"}}
+{{index . "name"}}
+{{end}}
+
+{{define "page_title"}}
+Wireguard Server Settings
+{{end}}
+
+{{define "page_content"}}
+<section class="content">
+    <div class="container-fluid">
+        <!-- <h5 class="mt-4 mb-2">Wireguard Server</h5> -->
+        <div class="row">
+            <!-- left column -->
+            <div class="col-md-6">
+                <div class="card card-success">
+                    <div class="card-header">
+                        <h3 class="card-title">Interfaces</h3>
+                    </div>
+                    <!-- /.card-header -->
+                    <!-- form start -->
+                    <form role="form" id="frm_server_interface" name="frm_server_interface">
+                        <div class="card-body">
+                            <div class="form-group">
+                                <label for="addresses" class="control-label">Server Interface Addresses</label>
+                                <input type="text" data-role="tagsinput" class="form-control" id="addresses" value="">
+                            </div>
+                            <div class="form-group">
+                                <label for="listen_port">Listen Port</label>
+                                <input type="text" class="form-control" id="listen_port" name="listen_port"
+                                    placeholder="Listen Port" value="{{ .serverInterface.ListenPort }}">
+                            </div>
+                        </div>
+                        <!-- /.card-body -->
+
+                        <div class="card-footer">
+                            <button type="submit" class="btn btn-success">Save</button>
+                        </div>
+                    </form>
+                </div>
+                <!-- /.card -->
+            </div>
+            <!-- right column -->
+            <div class="col-md-6">
+                <div class="card card-danger">
+                    <div class="card-header">
+                        <h3 class="card-title">Key Pair</h3>
+                    </div>
+                    <!-- /.card-header -->
+                    <!-- form start -->
+                    <form role="form">
+                        <div class="card-body">
+                            <div class="form-group">
+                                <label for="private_key">Private Key</label>
+                                <div class="input-group input-group">
+                                    <input type="text" class="form-control" id="private_key" placeholder="Private Key">
+                                    <span class="input-group-append">
+                                        <button type="button" class="btn btn-danger btn-flat">Show</button>
+                                    </span>
+                                </div>
+                            </div>
+                            <div class="form-group">
+                                <label for="public_key">Public Key</label>
+                                <input type="text" class="form-control" id="public_key" placeholder="Public Key">
+                            </div>
+                        </div>
+                        <!-- /.card-body -->
+
+                        <div class="card-footer">
+                            <button type="submit" class="btn btn-danger">Regenerate</button>
+                        </div>
+                    </form>
+                </div>
+                <!-- /.card -->
+            </div>
+        </div>
+        <!-- /.row -->
+    </div>
+</section>
+{{end}}
+
+{{define "bottom_js"}}
+    <script>
+        function submitServerInterfaceSetting() {
+            var addresses = $("#addresses").val().split(",");
+            var listen_port = $("#listen_port").val();
+            var data = {"addresses": addresses, "listen_port": listen_port};
+
+            $.ajax({
+                cache: false,
+                method: 'POST',
+                url: '/wg-server/interfaces',
+                dataType: 'json',
+                contentType: "application/json",
+                data: JSON.stringify(data),
+                success: function(data) {
+                    $('#modal_new_client').modal('hide');
+                    toastr.success('Updated Wireguard server interface addresses successfully');
+                },
+                error: function(jqXHR, exception) {
+                    var responseJson = jQuery.parseJSON(jqXHR.responseText);
+                    toastr.error(responseJson['message']);
+                }
+            });
+        }
+    </script>
+    <script>
+        // Wireguard Interface Addresses tag input
+        $('#addresses').tagsInput({
+            'width': '100%',
+            // 'height': '75%',
+            'interactive': true,
+            'defaultText': 'Add More',
+            'removeWithBackspace': true,
+            'minChars': 0,
+            'maxChars': 18,
+            'placeholderColor': '#666666'
+        });
+
+        // Load server addresses to the form
+        {{range .serverInterface.Addresses}}
+        $('#addresses').addTag('{{.}}');
+        {{end}}
+
+        // Wireguard Interface Addresses form validation
+        $(document).ready(function () {
+            $.validator.setDefaults({
+                submitHandler: function () {
+                    submitServerInterfaceSetting();
+                }
+            });
+            $('#frm_server_interface').validate({
+                rules: {
+                    listen_port: {
+                        required: true,
+                        digits: true,
+                        range: [1, 65535]
+                    }
+                },
+                messages: {
+                    listen_port: {
+                        required: "Please enter a port",
+                        digits: "Port must be an integer",
+                        range: "Port must be in range 1..65535"
+                    }
+                },
+                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 d825eb0..b9556f0 100644
--- a/util/util.go
+++ b/util/util.go
@@ -40,7 +40,7 @@ func BuildClientConfig(client model.Client) string {
 	return strConfig
 }
 
-// ValidateCIDR to validate an network CIDR
+// ValidateCIDR to validate a network CIDR
 func ValidateCIDR(cidr string) bool {
 	_, _, err := net.ParseCIDR(cidr)
 	if err != nil {
@@ -49,8 +49,8 @@ func ValidateCIDR(cidr string) bool {
 	return true
 }
 
-// ValidateAllowedIPs to validate allowed ip addresses in CIDR format.
-func ValidateAllowedIPs(cidrs []string) bool {
+// ValidateCIDRList to validate a list of network CIDR
+func ValidateCIDRList(cidrs []string) bool {
 	for _, cidr := range cidrs {
 		if ValidateCIDR(cidr) == false {
 			return false
@@ -58,3 +58,19 @@ func ValidateAllowedIPs(cidrs []string) bool {
 	}
 	return true
 }
+
+// ValidateAllowedIPs to validate allowed ip addresses in CIDR format
+func ValidateAllowedIPs(cidrs []string) bool {
+	if ValidateCIDRList(cidrs) == false {
+		return false
+	}
+	return true
+}
+
+// ValidateServerAddresses to validate allowed ip addresses in CIDR format
+func ValidateServerAddresses(cidrs []string) bool {
+	if ValidateCIDRList(cidrs) == false {
+		return false
+	}
+	return true
+}