Browse Source

Added csrf fix to some components ui

Toby Chui 7 months ago
parent
commit
5366237dcc

+ 16 - 16
api.go

@@ -22,11 +22,11 @@ import (
 
 var requireAuth = true
 
-func initAPIs() {
-
+func initAPIs(targetMux *http.ServeMux) {
 	authRouter := auth.NewManagedHTTPRouter(auth.RouterOption{
 		AuthAgent:   authAgent,
 		RequireAuth: requireAuth,
+		TargetMux:   targetMux,
 		DeniedHandler: func(w http.ResponseWriter, r *http.Request) {
 			http.Error(w, "401 - Unauthorized", http.StatusUnauthorized)
 		},
@@ -37,12 +37,12 @@ func initAPIs() {
 	if development {
 		fs = http.FileServer(http.Dir("web/"))
 	}
-	//Add a layer of middleware for advance  control
+	//Add a layer of middleware for advance control
 	advHandler := FSHandler(fs)
-	http.Handle("/", advHandler)
+	targetMux.Handle("/", advHandler)
 
 	//Authentication APIs
-	registerAuthAPIs(requireAuth)
+	registerAuthAPIs(requireAuth, targetMux)
 
 	//Reverse proxy
 	authRouter.HandleFunc("/api/proxy/enable", ReverseProxyHandleOnOff)
@@ -187,8 +187,8 @@ func initAPIs() {
 	authRouter.HandleFunc("/api/tools/fwdproxy/port", forwardProxy.HandlePort)
 
 	//Account Reset
-	http.HandleFunc("/api/account/reset", HandleAdminAccountResetEmail)
-	http.HandleFunc("/api/account/new", HandleNewPasswordSetup)
+	targetMux.HandleFunc("/api/account/reset", HandleAdminAccountResetEmail)
+	targetMux.HandleFunc("/api/account/new", HandleNewPasswordSetup)
 
 	//ACME & Auto Renewer
 	authRouter.HandleFunc("/api/acme/listExpiredDomains", acmeHandler.HandleGetExpiredDomains)
@@ -228,7 +228,7 @@ func initAPIs() {
 	authRouter.HandleFunc("/api/docker/containers", DockerUXOptimizer.HandleDockerContainersList)
 
 	//Others
-	http.HandleFunc("/api/info/x", HandleZoraxyInfo)
+	targetMux.HandleFunc("/api/info/x", HandleZoraxyInfo)
 	authRouter.HandleFunc("/api/info/geoip", HandleGeoIpLookup)
 	authRouter.HandleFunc("/api/conf/export", ExportConfigAsZip)
 	authRouter.HandleFunc("/api/conf/import", ImportConfigFromZip)
@@ -243,18 +243,18 @@ func initAPIs() {
 }
 
 // Function to renders Auth related APIs
-func registerAuthAPIs(requireAuth bool) {
+func registerAuthAPIs(requireAuth bool, targetMux *http.ServeMux) {
 	//Auth APIs
-	http.HandleFunc("/api/auth/login", authAgent.HandleLogin)
-	http.HandleFunc("/api/auth/logout", authAgent.HandleLogout)
-	http.HandleFunc("/api/auth/checkLogin", func(w http.ResponseWriter, r *http.Request) {
+	targetMux.HandleFunc("/api/auth/login", authAgent.HandleLogin)
+	targetMux.HandleFunc("/api/auth/logout", authAgent.HandleLogout)
+	targetMux.HandleFunc("/api/auth/checkLogin", func(w http.ResponseWriter, r *http.Request) {
 		if requireAuth {
 			authAgent.CheckLogin(w, r)
 		} else {
 			utils.SendJSONResponse(w, "true")
 		}
 	})
-	http.HandleFunc("/api/auth/username", func(w http.ResponseWriter, r *http.Request) {
+	targetMux.HandleFunc("/api/auth/username", func(w http.ResponseWriter, r *http.Request) {
 		username, err := authAgent.GetUserName(w, r)
 		if err != nil {
 			http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
@@ -264,12 +264,12 @@ func registerAuthAPIs(requireAuth bool) {
 		js, _ := json.Marshal(username)
 		utils.SendJSONResponse(w, string(js))
 	})
-	http.HandleFunc("/api/auth/userCount", func(w http.ResponseWriter, r *http.Request) {
+	targetMux.HandleFunc("/api/auth/userCount", func(w http.ResponseWriter, r *http.Request) {
 		uc := authAgent.GetUserCounts()
 		js, _ := json.Marshal(uc)
 		utils.SendJSONResponse(w, string(js))
 	})
-	http.HandleFunc("/api/auth/register", func(w http.ResponseWriter, r *http.Request) {
+	targetMux.HandleFunc("/api/auth/register", func(w http.ResponseWriter, r *http.Request) {
 		if authAgent.GetUserCounts() == 0 {
 			//Allow register root admin
 			authAgent.HandleRegisterWithoutEmail(w, r, func(username, reserved string) {
@@ -280,7 +280,7 @@ func registerAuthAPIs(requireAuth bool) {
 			utils.SendErrorResponse(w, "Root management account already exists")
 		}
 	})
-	http.HandleFunc("/api/auth/changePassword", func(w http.ResponseWriter, r *http.Request) {
+	targetMux.HandleFunc("/api/auth/changePassword", func(w http.ResponseWriter, r *http.Request) {
 		username, err := authAgent.GetUserName(w, r)
 		if err != nil {
 			http.Error(w, "401 - Unauthorized", http.StatusUnauthorized)

+ 12 - 11
cert.go

@@ -182,27 +182,28 @@ func handleToggleTLSProxy(w http.ResponseWriter, r *http.Request) {
 		sysdb.Read("settings", "usetls", &currentTlsSetting)
 	}
 
-	newState, err := utils.PostPara(r, "set")
-	if err != nil {
-		//No setting. Get the current status
+	if r.Method == http.MethodGet {
+		//Get the current status
 		js, _ := json.Marshal(currentTlsSetting)
 		utils.SendJSONResponse(w, string(js))
-	} else {
-		if newState == "true" {
+	} else if r.Method == http.MethodPost {
+		newState, err := utils.PostBool(r, "set")
+		if err != nil {
+			utils.SendErrorResponse(w, "new state not set or invalid")
+			return
+		}
+		if newState {
 			sysdb.Write("settings", "usetls", true)
 			SystemWideLogger.Println("Enabling TLS mode on reverse proxy")
 			dynamicProxyRouter.UpdateTLSSetting(true)
-		} else if newState == "false" {
+		} else {
 			sysdb.Write("settings", "usetls", false)
 			SystemWideLogger.Println("Disabling TLS mode on reverse proxy")
 			dynamicProxyRouter.UpdateTLSSetting(false)
-		} else {
-			utils.SendErrorResponse(w, "invalid state given. Only support true or false")
-			return
 		}
-
 		utils.SendOK(w)
-
+	} else {
+		http.Error(w, "405 - Method not allowed", http.StatusMethodNotAllowed)
 	}
 }
 

+ 1 - 0
go.mod

@@ -95,6 +95,7 @@ require (
 	github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
 	github.com/googleapis/gax-go/v2 v2.12.2 // indirect
 	github.com/gophercloud/gophercloud v1.0.0 // indirect
+	github.com/gorilla/csrf v1.7.2 // indirect
 	github.com/gorilla/css v1.0.1 // indirect
 	github.com/gorilla/securecookie v1.1.2 // indirect
 	github.com/hashicorp/errwrap v1.0.0 // indirect

+ 2 - 0
go.sum

@@ -317,6 +317,8 @@ github.com/googleapis/gax-go/v2 v2.12.2/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7
 github.com/gophercloud/gophercloud v1.0.0 h1:9nTGx0jizmHxDobe4mck89FyQHVyA3CaXLIUSGJjP9k=
 github.com/gophercloud/gophercloud v1.0.0/go.mod h1:Q8fZtyi5zZxPS/j9aj3sSxtvj41AdQMDwyo1myduD5c=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI=
+github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk=
 github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
 github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
 github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=

+ 13 - 6
main.go

@@ -12,6 +12,7 @@ import (
 	"time"
 
 	"github.com/google/uuid"
+	"github.com/gorilla/csrf"
 	"imuslab.com/zoraxy/mod/access"
 	"imuslab.com/zoraxy/mod/acme"
 	"imuslab.com/zoraxy/mod/auth"
@@ -72,10 +73,12 @@ var (
 	/*
 		Handler Modules
 	*/
-	sysdb          *database.Database     //System database
-	authAgent      *auth.AuthAgent        //Authentication agent
-	tlsCertManager *tlscert.Manager       //TLS / SSL management
-	redirectTable  *redirection.RuleTable //Handle special redirection rule sets
+	sysdb          *database.Database              //System database
+	authAgent      *auth.AuthAgent                 //Authentication agent
+	tlsCertManager *tlscert.Manager                //TLS / SSL management
+	redirectTable  *redirection.RuleTable          //Handle special redirection rule sets
+	webminPanelMux *http.ServeMux                  //Server mux for handling webmin panel APIs
+	csrfMiddleware func(http.Handler) http.Handler //CSRF protection middleware
 
 	pathRuleHandler    *pathrule.Handler         //Handle specific path blocking or custom headers
 	geodbStore         *geodb.Store              //GeoIP database, for resolving IP into country code
@@ -176,12 +179,16 @@ func main() {
 	}
 	nodeUUID = string(uuidBytes)
 
+	//Create a new webmin mux and csrf middleware layer
+	webminPanelMux := http.NewServeMux()
+	csrfMiddleware := csrf.Protect([]byte(nodeUUID))
+
 	//Startup all modules
 	startupSequence()
 
 	//Initiate management interface APIs
 	requireAuth = !(*noauth)
-	initAPIs()
+	initAPIs(webminPanelMux)
 
 	//Start the reverse proxy server in go routine
 	go func() {
@@ -194,7 +201,7 @@ func main() {
 	finalSequence()
 
 	SystemWideLogger.Println("Zoraxy started. Visit control panel at http://localhost" + *webUIPort)
-	err = http.ListenAndServe(*webUIPort, nil)
+	err = http.ListenAndServe(*webUIPort, csrfMiddleware(webminPanelMux))
 
 	if err != nil {
 		log.Fatal(err)

+ 24 - 10
mod/auth/router.go

@@ -10,7 +10,7 @@ type RouterOption struct {
 	AuthAgent     *AuthAgent
 	RequireAuth   bool                                     //This router require authentication
 	DeniedHandler func(http.ResponseWriter, *http.Request) //Things to do when request is rejected
-
+	TargetMux     *http.ServeMux
 }
 
 type RouterDef struct {
@@ -35,17 +35,31 @@ func (router *RouterDef) HandleFunc(endpoint string, handler func(http.ResponseW
 	authAgent := router.option.AuthAgent
 
 	//OK. Register handler
-	http.HandleFunc(endpoint, func(w http.ResponseWriter, r *http.Request) {
-		//Check authentication of the user
-		if router.option.RequireAuth {
-			authAgent.HandleCheckAuth(w, r, func(w http.ResponseWriter, r *http.Request) {
+	if router.option.TargetMux == nil {
+		http.HandleFunc(endpoint, func(w http.ResponseWriter, r *http.Request) {
+			//Check authentication of the user
+			if router.option.RequireAuth {
+				authAgent.HandleCheckAuth(w, r, func(w http.ResponseWriter, r *http.Request) {
+					handler(w, r)
+				})
+			} else {
+				handler(w, r)
+			}
+
+		})
+	} else {
+		router.option.TargetMux.HandleFunc(endpoint, func(w http.ResponseWriter, r *http.Request) {
+			//Check authentication of the user
+			if router.option.RequireAuth {
+				authAgent.HandleCheckAuth(w, r, func(w http.ResponseWriter, r *http.Request) {
+					handler(w, r)
+				})
+			} else {
 				handler(w, r)
-			})
-		} else {
-			handler(w, r)
-		}
+			}
 
-	})
+		})
+	}
 
 	router.endpoints[endpoint] = handler
 

+ 0 - 3
mod/dockerux/docker.go

@@ -3,8 +3,6 @@
 
 package dockerux
 
-/* Windows docker optimizer*/
-
 import (
 	"context"
 	"encoding/json"
@@ -16,7 +14,6 @@ import (
 	"imuslab.com/zoraxy/mod/utils"
 )
 
-// Windows build not support docker
 func (d *UXOptimizer) HandleDockerAvailable(w http.ResponseWriter, r *http.Request) {
 	js, _ := json.Marshal(d.RunninInDocker)
 	utils.SendJSONResponse(w, string(js))

+ 25 - 11
reverseproxy.go

@@ -572,7 +572,7 @@ func ReverseProxyHandleAlias(w http.ResponseWriter, r *http.Request) {
 }
 
 func DeleteProxyEndpoint(w http.ResponseWriter, r *http.Request) {
-	ep, err := utils.GetPara(r, "ep")
+	ep, err := utils.PostPara(r, "ep")
 	if err != nil {
 		utils.SendErrorResponse(w, "Invalid ep given")
 		return
@@ -941,18 +941,22 @@ func ReverseProxyList(w http.ResponseWriter, r *http.Request) {
 
 // Handle port 80 incoming traffics
 func HandleUpdatePort80Listener(w http.ResponseWriter, r *http.Request) {
-	enabled, err := utils.GetPara(r, "enable")
-	if err != nil {
+	if r.Method == http.MethodGet {
 		//Load the current status
 		currentEnabled := false
-		err = sysdb.Read("settings", "listenP80", &currentEnabled)
+		err := sysdb.Read("settings", "listenP80", &currentEnabled)
 		if err != nil {
 			utils.SendErrorResponse(w, err.Error())
 			return
 		}
 		js, _ := json.Marshal(currentEnabled)
 		utils.SendJSONResponse(w, string(js))
-	} else {
+	} else if r.Method == http.MethodPost {
+		enabled, err := utils.PostPara(r, "enable")
+		if err != nil {
+			utils.SendErrorResponse(w, "enable state not set")
+			return
+		}
 		if enabled == "true" {
 			sysdb.Write("settings", "listenP80", true)
 			SystemWideLogger.Println("Enabling port 80 listener")
@@ -965,38 +969,48 @@ func HandleUpdatePort80Listener(w http.ResponseWriter, r *http.Request) {
 			utils.SendErrorResponse(w, "invalid mode given: "+enabled)
 		}
 		utils.SendOK(w)
+	} else {
+		http.Error(w, "405 - Method not allowed", http.StatusMethodNotAllowed)
 	}
+
 }
 
 // Handle https redirect
 func HandleUpdateHttpsRedirect(w http.ResponseWriter, r *http.Request) {
-	useRedirect, err := utils.GetPara(r, "set")
-	if err != nil {
+	if r.Method == http.MethodGet {
 		currentRedirectToHttps := false
 		//Load the current status
-		err = sysdb.Read("settings", "redirect", &currentRedirectToHttps)
+		err := sysdb.Read("settings", "redirect", &currentRedirectToHttps)
 		if err != nil {
 			utils.SendErrorResponse(w, err.Error())
 			return
 		}
 		js, _ := json.Marshal(currentRedirectToHttps)
 		utils.SendJSONResponse(w, string(js))
-	} else {
+	} else if r.Method == http.MethodPost {
+		useRedirect, err := utils.PostBool(r, "set")
+		if err != nil {
+			utils.SendErrorResponse(w, "status not set")
+			return
+		}
+
 		if dynamicProxyRouter.Option.Port == 80 {
 			utils.SendErrorResponse(w, "This option is not available when listening on port 80")
 			return
 		}
-		if useRedirect == "true" {
+		if useRedirect {
 			sysdb.Write("settings", "redirect", true)
 			SystemWideLogger.Println("Updating force HTTPS redirection to true")
 			dynamicProxyRouter.UpdateHttpToHttpsRedirectSetting(true)
-		} else if useRedirect == "false" {
+		} else {
 			sysdb.Write("settings", "redirect", false)
 			SystemWideLogger.Println("Updating force HTTPS redirection to false")
 			dynamicProxyRouter.UpdateHttpToHttpsRedirectSetting(false)
 		}
 
 		utils.SendOK(w)
+	} else {
+		http.Error(w, "405 - Method not allowed", http.StatusMethodNotAllowed)
 	}
 }
 

+ 59 - 1
router.go

@@ -4,9 +4,11 @@ import (
 	"fmt"
 	"net/http"
 	"net/url"
+	"os"
 	"path/filepath"
 	"strings"
 
+	"github.com/gorilla/csrf"
 	"imuslab.com/zoraxy/mod/sshprox"
 )
 
@@ -42,11 +44,15 @@ func FSHandler(handler http.Handler) http.Handler {
 
 		// Allow access to /script/*, /img/pubic/* and /login.html without authentication
 		if strings.HasPrefix(r.URL.Path, ppf("/script/")) || strings.HasPrefix(r.URL.Path, ppf("/img/public/")) || r.URL.Path == ppf("/login.html") || r.URL.Path == ppf("/reset.html") || r.URL.Path == ppf("/favicon.png") {
+			if isHTMLFilePath(r.URL.Path) {
+				handleInjectHTML(w, r, r.URL.Path)
+				return
+			}
 			handler.ServeHTTP(w, r)
 			return
 		}
 
-		// check authentication
+		// Check authentication
 		if !authAgent.CheckAuth(r) && requireAuth {
 			http.Redirect(w, r, ppf("/login.html"), http.StatusTemporaryRedirect)
 			return
@@ -77,6 +83,10 @@ func FSHandler(handler http.Handler) http.Handler {
 		}
 
 		//Authenticated
+		if isHTMLFilePath(r.URL.Path) {
+			handleInjectHTML(w, r, r.URL.Path)
+			return
+		}
 		handler.ServeHTTP(w, r)
 	})
 }
@@ -88,3 +98,51 @@ func ppf(relativeFilepath string) string {
 	}
 	return relativeFilepath
 }
+
+func isHTMLFilePath(requestURI string) bool {
+	return strings.HasSuffix(requestURI, ".html") || strings.HasSuffix(requestURI, "/")
+}
+
+// Serve the html file with template token injected
+func handleInjectHTML(w http.ResponseWriter, r *http.Request, relativeFilepath string) {
+	// Read the HTML file
+	var content []byte
+	var err error
+	if len(relativeFilepath) > 0 && relativeFilepath[len(relativeFilepath)-1:] == "/" {
+		relativeFilepath = relativeFilepath + "index.html"
+	}
+	if development {
+		//Load from disk
+		targetFilePath := strings.ReplaceAll(filepath.Join("web/", relativeFilepath), "\\", "/")
+		content, err = os.ReadFile(targetFilePath)
+		if err != nil {
+			http.Error(w, "Internal Server Error", http.StatusInternalServerError)
+			return
+		}
+	} else {
+		//Load from embedded fs
+		content, err = webres.ReadFile(relativeFilepath)
+		if err != nil {
+			http.Error(w, "Internal Server Error", http.StatusInternalServerError)
+			return
+		}
+	}
+
+	// Convert the file content to a string
+	htmlContent := string(content)
+
+	//Defeine the system template for this request
+	templateStrings := map[string]string{
+		".csrfToken": csrf.Token(r),
+	}
+
+	// Replace template tokens in the HTML content
+	for key, value := range templateStrings {
+		placeholder := "{{" + key + "}}"
+		htmlContent = strings.ReplaceAll(htmlContent, placeholder, value)
+	}
+
+	// Write the modified HTML content to the response
+	w.Header().Set("Content-Type", "text/html")
+	w.Write([]byte(htmlContent))
+}

+ 1 - 1
start.go

@@ -292,7 +292,7 @@ func startupSequence() {
 
 	/* Docker UX Optimizer */
 	if runtime.GOOS == "windows" && *runningInDocker {
-		SystemWideLogger.PrintAndLog("WARNING", "Invalid start flag combination: docker=true && runtime.GOOS == windows. Running in docker UX development mode.", nil)
+		SystemWideLogger.PrintAndLog("warning", "Invalid start flag combination: docker=true && runtime.GOOS == windows. Running in docker UX development mode.", nil)
 	}
 	DockerUXOptimizer = dockerux.NewDockerOptimizer(*runningInDocker, SystemWideLogger)
 

+ 11 - 10
web/components/access.html

@@ -1000,7 +1000,7 @@
     */
     function enableBlacklist() {
         var isChecked = $('#enableBlacklist').is(':checked');
-        $.ajax({
+        $.cjax({
             type: 'POST',
             url: '/api/blacklist/enable',
             data: { enable: isChecked, id: currentEditingAccessRule},
@@ -1028,9 +1028,10 @@
         let counter = 0;
         for(var i = 0; i < ccs.length; i++){
             let thisCountryCode = ccs[i];
-            $.ajax({
+            $.cjax({
                 type: "POST",
                 url: "/api/blacklist/country/add",
+                method: "POST",
                 data: { cc: thisCountryCode, id: currentEditingAccessRule},
                 success: function(response) {
                     if (response.error != undefined){
@@ -1066,7 +1067,7 @@
     function removeFromBannedList(countryCode){
         countryCode = countryCode.toLowerCase();
         let countryName = getCountryName(countryCode);
-        $.ajax({
+        $.cjax({
             url: "/api/blacklist/country/remove",
             method: "POST",
             data: { cc: countryCode, id: currentEditingAccessRule},
@@ -1097,7 +1098,7 @@
             }
         }
 
-        $.ajax({
+        $.cjax({
             url: "/api/blacklist/ip/add",
             type: "POST",
             data: {ip: targetIp.toLowerCase(), id: currentEditingAccessRule},
@@ -1119,7 +1120,7 @@
 
     function removeIpBlacklist(ipaddr){
         if (confirm("Confirm remove blacklist for " + ipaddr + " ?")){
-            $.ajax({
+            $.cjax({
                 url: "/api/blacklist/ip/remove",
                 type: "POST",
                 data: {ip: ipaddr.toLowerCase(), id: currentEditingAccessRule},
@@ -1143,7 +1144,7 @@
     */
     function enableWhitelist() {
         var isChecked = $('#enableWhitelist').is(':checked');
-        $.ajax({
+        $.cjax({
             type: 'POST',
             url: '/api/whitelist/enable',
             data: { enable: isChecked , id: currentEditingAccessRule},
@@ -1165,7 +1166,7 @@
         let counter = 0;
         for(var i = 0; i < ccs.length; i++){
             let thisCountryCode = ccs[i];
-            $.ajax({
+            $.cjax({
                 type: "POST",
                 url: "/api/whitelist/country/add",
                 data: { cc: thisCountryCode , id: currentEditingAccessRule},
@@ -1199,7 +1200,7 @@
     function removeFromWhiteList(countryCode){
         if (confirm("Confirm removing " + getCountryName(countryCode) + " from whitelist?")){
             countryCode = countryCode.toLowerCase(); 
-            $.ajax({
+            $.cjax({
                 url: "/api/whitelist/country/remove",
                 method: "POST",
                 data: { cc: countryCode , id: currentEditingAccessRule},
@@ -1230,7 +1231,7 @@
             }
         }
 
-        $.ajax({
+        $.cjax({
             url: "/api/whitelist/ip/add",
             type: "POST",
             data: {ip: targetIp.toLowerCase(), "comment": remarks, id: currentEditingAccessRule},
@@ -1253,7 +1254,7 @@
 
     function removeIpWhitelist(ipaddr){
         if (confirm("Confirm remove whitelist for " + ipaddr + " ?")){
-            $.ajax({
+            $.cjax({
                 url: "/api/whitelist/ip/remove",
                 type: "POST",
                 data: {ip: ipaddr.toLowerCase(), id: currentEditingAccessRule},

+ 3 - 3
web/components/cert.html

@@ -257,7 +257,7 @@
     //Delete the certificate by its domain
     function deleteCertificate(domain){
         if (confirm("Confirm delete certificate for " + domain + " ?")){
-            $.ajax({
+            $.cjax({
                 url: "/api/cert/delete",
                 method: "POST",
                 data: {domain: domain},
@@ -316,7 +316,7 @@
             return;
         }
 
-        $.ajax({
+        $.cjax({
             url: "/api/acme/autoRenew/email",
             method: "POST",
             data: {"set": newDefaultEmail},
@@ -330,7 +330,7 @@
             }
         });
 
-        $.ajax({
+        $.cjax({
             url: "/api/acme/autoRenew/ca",
             data: {"set": newDefaultCA},
             method: "POST",

+ 22 - 2
web/components/httprp.html

@@ -400,7 +400,7 @@
         let rateLimit = $(row).find(".RateLimit").val();
         let bypassGlobalTLS = $(row).find(".BypassGlobalTLS")[0].checked;
 
-        $.ajax({
+        $.cjax({
             url: "/api/proxy/edit",
             method: "POST",
             data: {
@@ -422,6 +422,26 @@
             }
         })
     }
+
+    //Generic functions for delete rp endpoints 
+    function deleteEndpoint(epoint){
+        epoint = decodeURIComponent(epoint).hexDecode();
+        if (confirm("Confirm remove proxy for :" + epoint + "?")){
+            $.cjax({
+                url: "/api/proxy/del",
+                method: "POST",
+                data: {ep: epoint},
+                success: function(data){
+                    if (data.error != undefined){
+                        listProxyEndpoints();
+                        msgbox("Proxy Rule Deleted", true);
+                        reloadUptimeList();
+                    }
+                }
+            })
+        }
+    }
+    
     
     /* button events */
     function editBasicAuthCredentials(uuid){
@@ -474,7 +494,7 @@
     function handleProxyRuleToggle(object){
         let endpointUUID = $(object).attr("eptuuid");
         let isChecked = object.checked;
-        $.ajax({
+        $.cjax({
             url: "/api/proxy/toggle",
             data: {
                 "ep": endpointUUID,

+ 4 - 3
web/components/redirection.html

@@ -116,7 +116,7 @@
         let forwardChildpath = document.querySelector('input[name="forward-childpath"]').checked;
         let redirectType = document.querySelector('input[name="redirect-type"]:checked').value;
 
-        $.ajax({
+        $.cjax({
             url: "/api/redirect/add", 
             method: "POST",
             data: {
@@ -141,7 +141,7 @@
       let targetURL = $(obj).attr("rurl");
       targetURL = JSON.parse(decodeURIComponent(targetURL));
       if (confirm("Confirm remove redirection from " + targetURL + " ?")){
-        $.ajax({
+        $.cjax({
               url: "/api/redirect/delete", 
               method: "POST",
               data: {
@@ -191,8 +191,9 @@
 
         //Bind event to the checkbox
         $("#redirectRegex").on("change", function(){
-          $.ajax({
+          $.cjax({
             url: "/api/redirect/regex",
+            method: "POST",
             data: {"enable": $(this)[0].checked},
             success: function(data){
               if (data.error != undefined){

+ 3 - 2
web/components/rproot.html

@@ -181,8 +181,9 @@
             targetDomain = targetDomain.substring(8);
             $("#proxyRoot").val(targetDomain);
         }
-       $.ajax({
+       $.cjax({
             url: "/api/proxy/tlscheck",
+            method: "POST",
             data: {url: targetDomain},
             success: function(data){
                 if (data.error != undefined){
@@ -232,7 +233,7 @@
         }
 
         //Create the endpoint by calling add
-        $.ajax({
+        $.cjax({
             url: "/api/proxy/add",
             data: {
                 "type": "root", 

+ 4 - 19
web/components/rules.html

@@ -212,8 +212,9 @@
         }
 
         //Create the endpoint by calling add
-        $.ajax({
+        $.cjax({
             url: "/api/proxy/add",
+            method: "POST",
             data: {
                 type: "host",
                 rootname: rootname, 
@@ -270,22 +271,6 @@
         
     }
 
-    //Generic functions for delete rp endpoints 
-    function deleteEndpoint(epoint){
-        epoint = decodeURIComponent(epoint).hexDecode();
-        if (confirm("Confirm remove proxy for :" + epoint + "?")){
-            $.ajax({
-                url: "/api/proxy/del",
-                data: {ep: epoint, },
-                success: function(){
-                    listProxyEndpoints();
-                    msgbox("Proxy Rule Deleted", true);
-                    reloadUptimeList();
-                }
-            })
-        }
-    }
-
     //Clearn the proxy target value, make sure user do not enter http:// or https://
     //and auto select TLS checkbox if https:// exists
     function autoFillTargetTLS(input){
@@ -307,12 +292,12 @@
 
     //Automatic check if the site require TLS and check the checkbox if needed
     function autoCheckTls(targetDomain){
-       $.ajax({
+       $.cjax({
             url: "/api/proxy/tlscheck",
             data: {url: targetDomain},
             success: function(data){
                 if (data.error != undefined){
-
+                    msgbox(data.error, false);
                 }else if (data == "https"){
                     $("#reqTls").parent().checkbox("set checked");
                 }else if (data == "http"){

+ 42 - 23
web/components/status.html

@@ -315,26 +315,39 @@
 
     //Start and stop service button
     function startService(){
-        $.post("/api/proxy/enable", {enable: true}, function(data){
-            if (data.error != undefined){
-                msgbox(data.error, false, 5000);
+        $.cjax({
+            url: "/api/proxy/enable",
+            method: "POST",
+            data: {enable: true},
+            success: function(data){
+                if (data.error != undefined){
+                    msgbox(data.error, false, 5000);
+                }
+                initRPStaste();
             }
-            initRPStaste();
+
         });
     }   
 
     function stopService(){
-        $.post("/api/proxy/enable", {enable: false}, function(data){
-            if (data.error != undefined){
-                msgbox(data.error, false, 5000);
+        $.cjax({
+            url: "/api/proxy/enable",
+            method: "POST",
+            data: {enable: false},
+            success: function(data){
+                if (data.error != undefined){
+                    msgbox(data.error, false, 5000);
+                }
+                initRPStaste();
             }
-            initRPStaste();
+
         });
     }
 
     function handleP80ListenerStateChange(enabled){
-        $.ajax({
+        $.cjax({
             url: "/api/proxy/listenPort80",
+            method: "POST",
             data: {"enable": enabled},
             success: function(data){
                 if (data.error != undefined){
@@ -361,16 +374,21 @@
             return;
         }
 
-        $.post("/api/proxy/setIncoming", {incoming: newPortValue}, function(data){
-            if (data.error != undefined){
-                msgbox(data.error, false, 5000);
-                return;
-            }
-            msgbox("Listening Port Updated");
-            initRPStaste();
+        $.cjax({
+            url: "/api/proxy/setIncoming",
+            method: "POST",
+            data: {incoming: newPortValue},
+            success: function(data){
+                if (data.error != undefined){
+                    msgbox(data.error, false, 5000);
+                    return;
+                }
+                msgbox("Listening Port Updated");
+                initRPStaste();
 
-            //Hide the reminder text
-            $("#applyButtonReminder").hide();
+                //Hide the reminder text
+                $("#applyButtonReminder").hide();
+            }
         });
     }
 
@@ -402,8 +420,9 @@
             //Initiate the input listener on the checkbox
             $("#redirect").find("input").on("change", function(){
                 let thisValue = $("#redirect").checkbox("is checked");
-                    $.ajax({
+                    $.cjax({
                         url: "/api/proxy/useHttpsRedirect",
+                        method: "POST",
                         data: {set: thisValue},
                         success: function(data){
                             if (data.error != undefined){
@@ -440,9 +459,10 @@
             //Bind events to the checkbox
             $("#tlsMinVer").find("input").on("change", function(){
                 let thisValue = $("#tlsMinVer").checkbox("is checked");
-                $.ajax({
+                $.cjax({
                     url: "/api/cert/tlsRequireLatest",
                     data: {"set": thisValue},
+                    method: "POST",
                     success: function(data){
                         if (data.error != undefined){
                             msgbox(data.error, false, 5000);
@@ -498,15 +518,14 @@
                 }else{
                     $(".tlsEnabledOnly").addClass('disabled');
                 }
-                $.ajax({
+                $.cjax({
                     url: "/api/cert/tls",
                     data: {set: thisValue},
                     success: function(data){
                         if (data.error != undefined){
-                            alert(data.error);
+                            msgbox(data.error, false);
                         }else{
                             //Updated
-                            
                             //Check for case if the port is invalid default ports
                             if ($("#incomingPort").val() == "80" && thisValue == true){
                                 confirmBox("Change listen port to :443?", function(choice){

+ 4 - 4
web/components/vdir.html

@@ -190,7 +190,7 @@
     function updateVDTargetTLSState(){
         var targetDomain = $("#virtualDirectoryDomain").val().trim();
         if (targetDomain != ""){
-            $.ajax({
+            $.cjax({
                 url: "/api/proxy/tlscheck",
                 data: {url: targetDomain},
                 success: function(data){
@@ -252,7 +252,7 @@
         }
 
         //Create a virtual directory endpoint
-        $.ajax({
+        $.cjax({
             url: "/api/proxy/vdir/add",
             method: "POST",
             data: {
@@ -295,7 +295,7 @@
             epType = "root";
             path = "";
         }
-        $.ajax({
+        $.cjax({
             url: "/api/proxy/vdir/del",
             method: "POST",
             data: {
@@ -384,7 +384,7 @@
 
         //console.log(mathingPath, newDomain, requireTLS, skipValidation);
 
-        $.ajax({
+        $.cjax({
             url: "/api/proxy/vdir/edit",
             method: "POST",
             data: {

+ 1 - 0
web/index.html

@@ -5,6 +5,7 @@
         <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"/>
         <meta charset="UTF-8">
         <meta name="theme-color" content="#4b75ff">
+        <meta name="zoraxy.csrf.Token" content="{{.csrfToken}}">
         <link rel="icon" type="image/png" href="./favicon.png" />
         <title>Control Panel | Zoraxy</title>
         <link rel="stylesheet" href="script/semantic/semantic.min.css">

+ 42 - 22
web/login.html

@@ -4,6 +4,7 @@
     <meta charset="UTF-8">
     <meta name="robots" content="noindex" />
     <meta name="viewport" content="width=device-width, initial-scale=1">
+    <meta name="zoraxy.csrf.Token" content="{{.csrfToken}}">
     <link rel="icon" type="image/png" href="./favicon.png" />
     <title>Login | Zoraxy</title>
     <link rel="stylesheet" href="script/semantic/semantic.min.css">
@@ -250,10 +251,10 @@
         });
 
         $("#regsiterbtn").on("click", function(event){
-            var username = $("#username").val();
-            var magic = $("#magic").val();
-            var repeatMagic = $("#repeatMagic").val();
-
+            let username = $("#username").val();
+            let magic = $("#magic").val();
+            let repeatMagic = $("#repeatMagic").val();
+            let csrfToken = document.getElementsByTagName("meta")["zoraxy.csrf.Token"].getAttribute("content");
             if (magic !== repeatMagic) {
                 alert("Password does not match");
                 return;
@@ -262,6 +263,9 @@
             $.ajax({
                 url: "/api/auth/register",
                 method: "POST",
+                beforeSend: function(request) {
+                    request.setRequestHeader("X-CSRF-Token",csrfToken);
+                },
                 data: {
                     username: username,
                     password: magic
@@ -297,29 +301,45 @@
 
         //Login system with the given username and password
         function login(){
-            var username = $("#username").val();
-            var magic = $("#magic").val();
-            var rmbme = document.getElementById("rmbme").checked;
+            let username = $("#username").val();
+            let magic = $("#magic").val();
+            let rmbme = document.getElementById("rmbme").checked;
+            let csrfToken = document.getElementsByTagName("meta")["zoraxy.csrf.Token"].getAttribute("content");
             $("#errmsg").stop().finish().slideUp("fast");
             $("input").addClass('disabled');
-            $.post(loginAddress, {"username": username, "password": magic, "rmbme": rmbme}).done(function(data){
-                if (data.error !== undefined){
-                    //Something went wrong during the login
-                    $("#errmsg").html(`<i class="red remove icon"></i> ${data.error}`);
-                    $("#errmsg").stop().finish().slideDown('fast');
-                }else if(data.redirect !== undefined){
-                    //LDAP Related Code
-                    window.location.href = data.redirect;
-                }else{
-                    //Login succeed
-                    if (redirectionAddress == ""){
-                        //Redirect back to index
-                        window.location.href = "./";
+            $.ajax({
+                url: loginAddress, 
+                type: "POST",
+                beforeSend: function(request) {
+                    request.setRequestHeader("X-CSRF-Token",csrfToken);
+                },
+                data: {
+                    "username": username, 
+                    "password": magic, 
+                    "rmbme": rmbme,
+                },
+                success: function(data){
+                    if (data.error !== undefined){
+                        //Something went wrong during the login
+                        $("#errmsg").html(`<i class="red remove icon"></i> ${data.error}`);
+                        $("#errmsg").stop().finish().slideDown('fast');
+                    }else if(data.redirect !== undefined){
+                        //LDAP Related Code
+                        window.location.href = data.redirect;
                     }else{
-                        window.location.href = redirectionAddress;
+                        //Login succeed
+                        if (redirectionAddress == ""){
+                            //Redirect back to index
+                            window.location.href = "./";
+                        }else{
+                            window.location.href = redirectionAddress;
+                        }
                     }
+                    $("input").removeClass('disabled');
+                },
+                error: function(){
+                    alert("Something went wrong.")
                 }
-                $("input").removeClass('disabled');
             });
 
         }

+ 14 - 1
web/script/utils.js

@@ -26,4 +26,17 @@ Object.defineProperty(String.prototype, 'capitalize', {
         return this.charAt(0).toUpperCase() + this.slice(1);
     },
     enumerable: false
-});
+});
+
+//Add a new function to jquery for ajax override with csrf token injected
+$.cjax = function(payload){
+    if (payload.method == "POST" || payload.type == "POST"){
+        //csrf token is required
+        let csrfToken = document.getElementsByTagName("meta")["zoraxy.csrf.Token"].getAttribute("content");
+        payload.headers = {
+            "X-CSRF-Token": csrfToken,
+        }
+    }
+
+    $.ajax(payload);
+}