From bddeae83657b6220ff0db65ebc5de2b5ff542443 Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Tue, 16 Jul 2024 22:08:51 +0800 Subject: [PATCH 01/18] Fixed manual renew certificate bug - Fixed manual renew certificate bug in wildcard certs - Updated version no --- src/main.go | 4 ++-- src/web/components/cert.html | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main.go b/src/main.go index fd239c1..5216a85 100644 --- a/src/main.go +++ b/src/main.go @@ -57,9 +57,9 @@ var enableAutoUpdate = flag.Bool("cfgupgrade", true, "Enable auto config upgrade var ( name = "Zoraxy" - version = "3.0.9" + version = "3.1.0" nodeUUID = "generic" //System uuid, in uuidv4 format - development = false //Set this to false to use embedded web fs + development = true //Set this to false to use embedded web fs bootTime = time.Now().Unix() /* diff --git a/src/web/components/cert.html b/src/web/components/cert.html index d1a473f..99fe7de 100644 --- a/src/web/components/cert.html +++ b/src/web/components/cert.html @@ -161,6 +161,7 @@

$(btn).addClass('disabled'); $(btn).html(``); } + obtainCertificate(domain, dns, defaultCA.trim(), function(succ){ if (btn != undefined){ $(btn).removeClass('disabled'); @@ -356,13 +357,16 @@

}); data.forEach(entry => { let isExpired = entry.RemainingDays <= 0; - + let entryDomainRenewKey = entry.Domain; + if (entryDomainRenewKey.includes("_.")){ + entryDomainRenewKey = entryDomainRenewKey.replace("_.","*."); + } $("#certifiedDomainList").append(` ${entry.Domain} ${entry.LastModifiedDate} ${entry.ExpireDate} (${!isExpired?entry.RemainingDays+" days left":"Expired"}) - + `); }); From 955a2232df6d0cefed084f0ca55c31ef7c0607b1 Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Thu, 18 Jul 2024 18:50:45 +0800 Subject: [PATCH 02/18] Update Makefile - Fixed bug in CICD pipeline --- src/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Makefile b/src/Makefile index e82e24e..f2a8ae3 100644 --- a/src/Makefile +++ b/src/Makefile @@ -19,7 +19,7 @@ clean: $(PLATFORMS): @echo "Building $(os)/$(arch)" - GOROOT_FINAL=Git/ GOOS=$(os) GOARCH=$(arch) $(if $(filter linux/arm,$(os)/$(arch)),GOARM=6,) go build -o './dist/zoraxy_$(os)_$(arch)' -ldflags "-s -w" -trimpath + GOROOT_FINAL=Git/ GOOS=$(os) GOARCH=$(arch) $(if $(filter linux/arm,$(os)/$(arch)),GOARM=6,) CGO_ENABLED="0" go build -o './dist/zoraxy_$(os)_$(arch)' -ldflags "-s -w" -trimpath # GOROOT_FINAL=Git/ GOOS=$(os) GOARCH=$(arch) GOARM=6 go build -o './dist/zoraxy_$(os)_$(arch)' -ldflags "-s -w" -trimpath From 245379e91f50635c53cb960b8e352b088863ae28 Mon Sep 17 00:00:00 2001 From: tobychui Date: Fri, 19 Jul 2024 10:21:26 +0800 Subject: [PATCH 03/18] Fixed #254 - Added uptime cleaning logic to update function --- src/main.go | 2 +- src/reverseproxy.go | 9 +++------ src/vdir.go | 2 ++ src/wrappers.go | 1 + 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main.go b/src/main.go index 5216a85..ca59d5d 100644 --- a/src/main.go +++ b/src/main.go @@ -59,7 +59,7 @@ var ( name = "Zoraxy" version = "3.1.0" nodeUUID = "generic" //System uuid, in uuidv4 format - development = true //Set this to false to use embedded web fs + development = false //Set this to false to use embedded web fs bootTime = time.Now().Unix() /* diff --git a/src/reverseproxy.go b/src/reverseproxy.go index 2e5c1d7..90a9f77 100644 --- a/src/reverseproxy.go +++ b/src/reverseproxy.go @@ -509,6 +509,9 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) { //Save it to file SaveReverseProxyConfig(newProxyEndpoint) + //Update uptime monitor targets + UpdateUptimeMonitorTargets() + utils.SendOK(w) } @@ -589,12 +592,6 @@ func DeleteProxyEndpoint(w http.ResponseWriter, r *http.Request) { return } - //Update utm if exists - if uptimeMonitor != nil { - uptimeMonitor.Config.Targets = GetUptimeTargetsFromReverseProxyRules(dynamicProxyRouter) - uptimeMonitor.CleanRecords() - } - //Update uptime monitor UpdateUptimeMonitorTargets() diff --git a/src/vdir.go b/src/vdir.go index e5405bc..9bcced4 100644 --- a/src/vdir.go +++ b/src/vdir.go @@ -197,6 +197,8 @@ func ReverseProxyDeleteVdir(w http.ResponseWriter, r *http.Request) { return } + UpdateUptimeMonitorTargets() + utils.SendOK(w) } diff --git a/src/wrappers.go b/src/wrappers.go index bef6070..66098df 100644 --- a/src/wrappers.go +++ b/src/wrappers.go @@ -111,6 +111,7 @@ func HandleCountryDistrSummary(w http.ResponseWriter, r *http.Request) { func UpdateUptimeMonitorTargets() { if uptimeMonitor != nil { uptimeMonitor.Config.Targets = GetUptimeTargetsFromReverseProxyRules(dynamicProxyRouter) + uptimeMonitor.CleanRecords() go func() { uptimeMonitor.ExecuteUptimeCheck() }() From f4a5c905e7a5419f4768e9dae4c8687cca63af8b Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Sun, 21 Jul 2024 15:11:13 +0800 Subject: [PATCH 04/18] Fixed #256 - Added startup paramter to change the early renew days of certificates - Changed the default early renew days of certificates from 14 days to 30 days - Fixed vdir update not updating uptime monitor bug --- src/main.go | 1 + src/mod/acme/autorenew.go | 11 ++++++++--- src/mod/acme/utils.go | 5 +++-- src/reverseproxy.go | 9 +++------ src/start.go | 8 +++++++- src/vdir.go | 2 ++ src/wrappers.go | 1 + 7 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/main.go b/src/main.go index 5216a85..fc933a5 100644 --- a/src/main.go +++ b/src/main.go @@ -50,6 +50,7 @@ var ztAuthToken = flag.String("ztauth", "", "ZeroTier authtoken for the local no var ztAPIPort = flag.Int("ztport", 9993, "ZeroTier controller API port") var runningInDocker = flag.Bool("docker", false, "Run Zoraxy in docker compatibility mode") var acmeAutoRenewInterval = flag.Int("autorenew", 86400, "ACME auto TLS/SSL certificate renew check interval (seconds)") +var acmeCertAutoRenewDays = flag.Int("earlyrenew", 30, "Number of days to early renew a soon expiring certificate (days)") var enableHighSpeedGeoIPLookup = flag.Bool("fastgeoip", false, "Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)") var staticWebServerRoot = flag.String("webroot", "./www", "Static web server root folder. Only allow chnage in start paramters") var allowWebFileManager = flag.Bool("webfm", true, "Enable web file manager for static web server root folder") diff --git a/src/mod/acme/autorenew.go b/src/mod/acme/autorenew.go index c043dc4..a4134fc 100644 --- a/src/mod/acme/autorenew.go +++ b/src/mod/acme/autorenew.go @@ -34,6 +34,7 @@ type AutoRenewer struct { AcmeHandler *ACMEHandler RenewerConfig *AutoRenewConfig RenewTickInterval int64 + EarlyRenewDays int //How many days before cert expire to renew certificate TickerstopChan chan bool } @@ -44,11 +45,15 @@ type ExpiredCerts struct { // Create an auto renew agent, require config filepath and auto scan & renew interval (seconds) // Set renew check interval to 0 for auto (1 day) -func NewAutoRenewer(config string, certFolder string, renewCheckInterval int64, AcmeHandler *ACMEHandler) (*AutoRenewer, error) { +func NewAutoRenewer(config string, certFolder string, renewCheckInterval int64, earlyRenewDays int, AcmeHandler *ACMEHandler) (*AutoRenewer, error) { if renewCheckInterval == 0 { renewCheckInterval = 86400 //1 day } + if earlyRenewDays == 0 { + earlyRenewDays = 30 + } + //Load the config file. If not found, create one if !utils.FileExists(config) { //Create one @@ -277,7 +282,7 @@ func (a *AutoRenewer) CheckAndRenewCertificates() ([]string, error) { if err != nil { continue } - if CertExpireSoon(certBytes) || CertIsExpired(certBytes) { + if CertExpireSoon(certBytes, a.EarlyRenewDays) || CertIsExpired(certBytes) { //This cert is expired DNSName, err := ExtractDomains(certBytes) @@ -305,7 +310,7 @@ func (a *AutoRenewer) CheckAndRenewCertificates() ([]string, error) { if err != nil { continue } - if CertExpireSoon(certBytes) || CertIsExpired(certBytes) { + if CertExpireSoon(certBytes, a.EarlyRenewDays) || CertIsExpired(certBytes) { //This cert is expired DNSName, err := ExtractDomains(certBytes) diff --git a/src/mod/acme/utils.go b/src/mod/acme/utils.go index 1638044..e0af40f 100644 --- a/src/mod/acme/utils.go +++ b/src/mod/acme/utils.go @@ -81,13 +81,14 @@ func CertIsExpired(certBytes []byte) bool { return false } -func CertExpireSoon(certBytes []byte) bool { +// CertExpireSoon check if the given cert bytes will expires within the given number of days from now +func CertExpireSoon(certBytes []byte, numberOfDays int) bool { block, _ := pem.Decode(certBytes) if block != nil { cert, err := x509.ParseCertificate(block.Bytes) if err == nil { expirationDate := cert.NotAfter - threshold := 14 * 24 * time.Hour // 14 days + threshold := time.Duration(numberOfDays) * 24 * time.Hour timeRemaining := time.Until(expirationDate) if timeRemaining <= threshold { diff --git a/src/reverseproxy.go b/src/reverseproxy.go index 2e5c1d7..90a9f77 100644 --- a/src/reverseproxy.go +++ b/src/reverseproxy.go @@ -509,6 +509,9 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) { //Save it to file SaveReverseProxyConfig(newProxyEndpoint) + //Update uptime monitor targets + UpdateUptimeMonitorTargets() + utils.SendOK(w) } @@ -589,12 +592,6 @@ func DeleteProxyEndpoint(w http.ResponseWriter, r *http.Request) { return } - //Update utm if exists - if uptimeMonitor != nil { - uptimeMonitor.Config.Targets = GetUptimeTargetsFromReverseProxyRules(dynamicProxyRouter) - uptimeMonitor.CleanRecords() - } - //Update uptime monitor UpdateUptimeMonitorTargets() diff --git a/src/start.go b/src/start.go index 8884e29..44a971e 100644 --- a/src/start.go +++ b/src/start.go @@ -279,7 +279,13 @@ func startupSequence() { //Create a table just to store acme related preferences sysdb.NewTable("acmepref") acmeHandler = initACME() - acmeAutoRenewer, err = acme.NewAutoRenewer("./conf/acme_conf.json", "./conf/certs/", int64(*acmeAutoRenewInterval), acmeHandler) + acmeAutoRenewer, err = acme.NewAutoRenewer( + "./conf/acme_conf.json", + "./conf/certs/", + int64(*acmeAutoRenewInterval), + *acmeCertAutoRenewDays, + acmeHandler, + ) if err != nil { log.Fatal(err) } diff --git a/src/vdir.go b/src/vdir.go index e5405bc..9bcced4 100644 --- a/src/vdir.go +++ b/src/vdir.go @@ -197,6 +197,8 @@ func ReverseProxyDeleteVdir(w http.ResponseWriter, r *http.Request) { return } + UpdateUptimeMonitorTargets() + utils.SendOK(w) } diff --git a/src/wrappers.go b/src/wrappers.go index bef6070..66098df 100644 --- a/src/wrappers.go +++ b/src/wrappers.go @@ -111,6 +111,7 @@ func HandleCountryDistrSummary(w http.ResponseWriter, r *http.Request) { func UpdateUptimeMonitorTargets() { if uptimeMonitor != nil { uptimeMonitor.Config.Targets = GetUptimeTargetsFromReverseProxyRules(dynamicProxyRouter) + uptimeMonitor.CleanRecords() go func() { uptimeMonitor.ExecuteUptimeCheck() }() From b1c5bc29630750d9a687e2d451443c44858cc399 Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Sun, 21 Jul 2024 17:06:09 +0800 Subject: [PATCH 05/18] Fixed #255 - Added host header manual overwrite feature - Added toggle for automatic hop-by-hop header removing --- src/api.go | 2 + src/main.go | 2 +- src/mod/dynamicproxy/dpcore/dpcore.go | 27 +++- src/mod/dynamicproxy/dynamicproxy.go | 13 +- src/mod/dynamicproxy/proxyRequestHandler.go | 34 ++--- src/mod/dynamicproxy/typedef.go | 1 + src/reverseproxy.go | 143 ++++++++++++++++++++ src/web/snippet/customHeaders.html | 123 +++++++++++++++++ 8 files changed, 315 insertions(+), 30 deletions(-) diff --git a/src/api.go b/src/api.go index b9e86e8..0d2f7c9 100644 --- a/src/api.go +++ b/src/api.go @@ -77,6 +77,8 @@ func initAPIs() { authRouter.HandleFunc("/api/proxy/header/add", HandleCustomHeaderAdd) authRouter.HandleFunc("/api/proxy/header/remove", HandleCustomHeaderRemove) authRouter.HandleFunc("/api/proxy/header/handleHSTS", HandleHSTSState) + authRouter.HandleFunc("/api/proxy/header/handleHopByHop", HandleHopByHop) + authRouter.HandleFunc("/api/proxy/header/handleHostOverwrite", HandleHostOverwrite) authRouter.HandleFunc("/api/proxy/header/handlePermissionPolicy", HandlePermissionPolicy) //Reverse proxy auth related APIs authRouter.HandleFunc("/api/proxy/auth/exceptions/list", ListProxyBasicAuthExceptionPaths) diff --git a/src/main.go b/src/main.go index bcd49ec..fc933a5 100644 --- a/src/main.go +++ b/src/main.go @@ -60,7 +60,7 @@ var ( name = "Zoraxy" version = "3.1.0" nodeUUID = "generic" //System uuid, in uuidv4 format - development = false //Set this to false to use embedded web fs + development = true //Set this to false to use embedded web fs bootTime = time.Now().Unix() /* diff --git a/src/mod/dynamicproxy/dpcore/dpcore.go b/src/mod/dynamicproxy/dpcore/dpcore.go index 2c64eaa..c659435 100644 --- a/src/mod/dynamicproxy/dpcore/dpcore.go +++ b/src/mod/dynamicproxy/dpcore/dpcore.go @@ -57,6 +57,7 @@ type ReverseProxy struct { } type ResponseRewriteRuleSet struct { + /* Basic Rewrite Rulesets */ ProxyDomain string OriginalHost string UseTLS bool @@ -64,8 +65,13 @@ type ResponseRewriteRuleSet struct { PathPrefix string //Vdir prefix for root, / will be rewrite to this UpstreamHeaders [][]string DownstreamHeaders [][]string - NoRemoveHopByHop bool //Do not remove hop-by-hop headers, dangerous - Version string //Version number of Zoraxy, use for X-Proxy-By + + /* Advance Usecase Options */ + HostHeaderOverwrite string //Force overwrite of request "Host" header (advanced usecase) + NoRemoveHopByHop bool //Do not remove hop-by-hop headers (advanced usecase) + + /* System Information Payload */ + Version string //Version number of Zoraxy, use for X-Proxy-By } type requestCanceler interface { @@ -73,8 +79,8 @@ type requestCanceler interface { } type DpcoreOptions struct { - IgnoreTLSVerification bool - FlushInterval time.Duration + IgnoreTLSVerification bool //Disable all TLS verification when request pass through this proxy router + FlushInterval time.Duration //Duration to flush in normal requests. Stream request or keep-alive request will always flush with interval of -1 (immediately) } func NewDynamicProxyCore(target *url.URL, prepender string, dpcOptions *DpcoreOptions) *ReverseProxy { @@ -281,7 +287,10 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr outreq.Close = false //Only skip origin rewrite iff proxy target require TLS and it is external domain name like github.com - if !(rrr.UseTLS && isExternalDomainName(rrr.ProxyDomain)) { + if rrr.HostHeaderOverwrite != "" { + //Use user defined overwrite header value, see issue #255 + outreq.Host = rrr.HostHeaderOverwrite + } else if !(rrr.UseTLS && isExternalDomainName(rrr.ProxyDomain)) { // Always use the original host, see issue #164 outreq.Host = rrr.OriginalHost } @@ -291,7 +300,9 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr copyHeader(outreq.Header, req.Header) // Remove hop-by-hop headers. - removeHeaders(outreq.Header, rrr.NoCache) + if !rrr.NoRemoveHopByHop { + removeHeaders(outreq.Header, rrr.NoCache) + } // Add X-Forwarded-For Header. addXForwardedForHeader(outreq) @@ -313,7 +324,9 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr } // Remove hop-by-hop headers listed in the "Connection" header of the response, Remove hop-by-hop headers. - removeHeaders(res.Header, rrr.NoCache) + if !rrr.NoRemoveHopByHop { + removeHeaders(res.Header, rrr.NoCache) + } //Remove the User-Agent header if exists if _, ok := res.Header["User-Agent"]; ok { diff --git a/src/mod/dynamicproxy/dynamicproxy.go b/src/mod/dynamicproxy/dynamicproxy.go index 4f6e6a8..17f8523 100644 --- a/src/mod/dynamicproxy/dynamicproxy.go +++ b/src/mod/dynamicproxy/dynamicproxy.go @@ -158,12 +158,13 @@ func (router *Router) StartProxyService() error { router.logRequest(r, false, 404, "vdir-http", r.Host) } selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{ - ProxyDomain: selectedUpstream.OriginIpOrDomain, - OriginalHost: originalHostHeader, - UseTLS: selectedUpstream.RequireTLS, - NoRemoveHopByHop: sep.DisableHopByHopHeaderRemoval, - PathPrefix: "", - Version: sep.parent.Option.HostVersion, + ProxyDomain: selectedUpstream.OriginIpOrDomain, + OriginalHost: originalHostHeader, + UseTLS: selectedUpstream.RequireTLS, + HostHeaderOverwrite: sep.RequestHostOverwrite, + NoRemoveHopByHop: sep.DisableHopByHopHeaderRemoval, + PathPrefix: "", + Version: sep.parent.Option.HostVersion, }) return } diff --git a/src/mod/dynamicproxy/proxyRequestHandler.go b/src/mod/dynamicproxy/proxyRequestHandler.go index 389fb03..f4ae58d 100644 --- a/src/mod/dynamicproxy/proxyRequestHandler.go +++ b/src/mod/dynamicproxy/proxyRequestHandler.go @@ -157,15 +157,16 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe upstreamHeaders, downstreamHeaders := target.SplitInboundOutboundHeaders() err = selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{ - ProxyDomain: selectedUpstream.OriginIpOrDomain, - OriginalHost: originalHostHeader, - UseTLS: selectedUpstream.RequireTLS, - NoCache: h.Parent.Option.NoCache, - PathPrefix: "", - UpstreamHeaders: upstreamHeaders, - DownstreamHeaders: downstreamHeaders, - NoRemoveHopByHop: target.DisableHopByHopHeaderRemoval, - Version: target.parent.Option.HostVersion, + ProxyDomain: selectedUpstream.OriginIpOrDomain, + OriginalHost: originalHostHeader, + UseTLS: selectedUpstream.RequireTLS, + NoCache: h.Parent.Option.NoCache, + PathPrefix: "", + UpstreamHeaders: upstreamHeaders, + DownstreamHeaders: downstreamHeaders, + HostHeaderOverwrite: target.RequestHostOverwrite, + NoRemoveHopByHop: target.DisableHopByHopHeaderRemoval, + Version: target.parent.Option.HostVersion, }) var dnsError *net.DNSError @@ -224,13 +225,14 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe upstreamHeaders, downstreamHeaders := target.parent.SplitInboundOutboundHeaders() err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{ - ProxyDomain: target.Domain, - OriginalHost: originalHostHeader, - UseTLS: target.RequireTLS, - PathPrefix: target.MatchingPath, - UpstreamHeaders: upstreamHeaders, - DownstreamHeaders: downstreamHeaders, - Version: target.parent.parent.Option.HostVersion, + ProxyDomain: target.Domain, + OriginalHost: originalHostHeader, + UseTLS: target.RequireTLS, + PathPrefix: target.MatchingPath, + UpstreamHeaders: upstreamHeaders, + DownstreamHeaders: downstreamHeaders, + HostHeaderOverwrite: target.parent.RequestHostOverwrite, + Version: target.parent.parent.Option.HostVersion, }) var dnsError *net.DNSError diff --git a/src/mod/dynamicproxy/typedef.go b/src/mod/dynamicproxy/typedef.go index 2effa6c..16ebd45 100644 --- a/src/mod/dynamicproxy/typedef.go +++ b/src/mod/dynamicproxy/typedef.go @@ -132,6 +132,7 @@ type ProxyEndpoint struct { //Custom Headers UserDefinedHeaders []*UserDefinedHeader //Custom headers to append when proxying requests from this endpoint + RequestHostOverwrite string //If not empty, this domain will be used to overwrite the Host field in request header HSTSMaxAge int64 //HSTS max age, set to 0 for disable HSTS headers EnablePermissionPolicyHeader bool //Enable injection of permission policy header PermissionPolicy *permissionpolicy.PermissionsPolicy //Permission policy header diff --git a/src/reverseproxy.go b/src/reverseproxy.go index 90a9f77..cea0c50 100644 --- a/src/reverseproxy.go +++ b/src/reverseproxy.go @@ -1235,6 +1235,149 @@ func HandleCustomHeaderRemove(w http.ResponseWriter, r *http.Request) { } +func HandleHostOverwrite(w http.ResponseWriter, r *http.Request) { + domain, err := utils.PostPara(r, "domain") + if err != nil { + domain, err = utils.GetPara(r, "domain") + if err != nil { + utils.SendErrorResponse(w, "domain or matching rule not defined") + return + } + } + //Get the proxy endpoint object dedicated to this domain + targetProxyEndpoint, err := dynamicProxyRouter.LoadProxy(domain) + if err != nil { + utils.SendErrorResponse(w, "target endpoint not exists") + return + } + + if r.Method == http.MethodGet { + //Get the current host header + js, _ := json.Marshal(targetProxyEndpoint.RequestHostOverwrite) + utils.SendJSONResponse(w, string(js)) + } else if r.Method == http.MethodPost { + //Set the new host header + newHostname, _ := utils.PostPara(r, "hostname") + + //As this will require change in the proxy instance we are running + //we need to clone and respawn this proxy endpoint + newProxyEndpoint := targetProxyEndpoint.Clone() + newProxyEndpoint.RequestHostOverwrite = newHostname + //Save proxy endpoint + err = SaveReverseProxyConfig(newProxyEndpoint) + if err != nil { + utils.SendErrorResponse(w, err.Error()) + return + } + + //Spawn a new endpoint with updated dpcore + preparedEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(newProxyEndpoint) + if err != nil { + utils.SendErrorResponse(w, err.Error()) + return + } + + //Remove the old endpoint + err = targetProxyEndpoint.Remove() + if err != nil { + utils.SendErrorResponse(w, err.Error()) + return + } + + //Add the newly prepared endpoint to runtime + err = dynamicProxyRouter.AddProxyRouteToRuntime(preparedEndpoint) + if err != nil { + utils.SendErrorResponse(w, err.Error()) + return + } + + //Print log message + if newHostname != "" { + SystemWideLogger.Println("Updated " + domain + " hostname overwrite to: " + newHostname) + } else { + SystemWideLogger.Println("Removed " + domain + " hostname overwrite") + } + + utils.SendOK(w) + } else { + //Invalid method + http.Error(w, "405 - Method not allowed", http.StatusMethodNotAllowed) + } +} + +// HandleHopByHop get and set the hop by hop remover state +// note that it shows the DISABLE STATE of hop-by-hop remover, not the enable state +func HandleHopByHop(w http.ResponseWriter, r *http.Request) { + domain, err := utils.PostPara(r, "domain") + if err != nil { + domain, err = utils.GetPara(r, "domain") + if err != nil { + utils.SendErrorResponse(w, "domain or matching rule not defined") + return + } + } + + targetProxyEndpoint, err := dynamicProxyRouter.LoadProxy(domain) + if err != nil { + utils.SendErrorResponse(w, "target endpoint not exists") + return + } + + if r.Method == http.MethodGet { + //Get the current hop by hop header state + js, _ := json.Marshal(!targetProxyEndpoint.DisableHopByHopHeaderRemoval) + utils.SendJSONResponse(w, string(js)) + } else if r.Method == http.MethodPost { + //Set the hop by hop header state + enableHopByHopRemover, _ := utils.PostBool(r, "removeHopByHop") + + //As this will require change in the proxy instance we are running + //we need to clone and respawn this proxy endpoint + newProxyEndpoint := targetProxyEndpoint.Clone() + //Storage file use false as default, so disable removal = not enable remover + targetProxyEndpoint.DisableHopByHopHeaderRemoval = !enableHopByHopRemover + //Save proxy endpoint + err = SaveReverseProxyConfig(newProxyEndpoint) + if err != nil { + utils.SendErrorResponse(w, err.Error()) + return + } + + //Spawn a new endpoint with updated dpcore + preparedEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(newProxyEndpoint) + if err != nil { + utils.SendErrorResponse(w, err.Error()) + return + } + + //Remove the old endpoint + err = targetProxyEndpoint.Remove() + if err != nil { + utils.SendErrorResponse(w, err.Error()) + return + } + + //Add the newly prepared endpoint to runtime + err = dynamicProxyRouter.AddProxyRouteToRuntime(preparedEndpoint) + if err != nil { + utils.SendErrorResponse(w, err.Error()) + return + } + + //Print log message + if enableHopByHopRemover { + SystemWideLogger.Println("Enabled hop-by-hop headers removal on " + domain) + } else { + SystemWideLogger.Println("Disabled hop-by-hop headers removal on " + domain) + } + + utils.SendOK(w) + + } else { + http.Error(w, "405 - Method not allowed", http.StatusMethodNotAllowed) + } +} + // Handle view or edit HSTS states func HandleHSTSState(w http.ResponseWriter, r *http.Request) { domain, err := utils.PostPara(r, "domain") diff --git a/src/web/snippet/customHeaders.html b/src/web/snippet/customHeaders.html index b995638..522e08a 100644 --- a/src/web/snippet/customHeaders.html +++ b/src/web/snippet/customHeaders.html @@ -83,6 +83,41 @@

Edit Custom Header

+
+
+
+
+ + Advance Settings +
+
+
+
+

Settings in this section are for advanced users. Invalid settings might cause werid, unexpected behavior.

+
+
+

Overwrite Host Header

+

Manual override the automatic "Host" header rewrite logic. Leave empty for automatic.

+
+ + + +
+ +
+

Remove Hop-by-hop Headers

+

Remove headers like "Connection" and "Keep-Alive" from both upstream and downstream requests. Set to ON by default.

+
+ + +
+
+
+
+
+
+

HTTP Strict Transport Security

@@ -129,6 +164,7 @@

Permission Policy

\ No newline at end of file From f595da92a1d41ba3ab7c0721c59ec51828775f61 Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Wed, 24 Jul 2024 21:58:44 +0800 Subject: [PATCH 06/18] Fixed #267 - Added csrf middleware to management portal mux - Added csrf token to all html templates - Added csrf validation to all endpoints - Optimized some old endpoints implementation --- src/api.go | 32 +++++------ src/cert.go | 23 ++++---- src/go.mod | 1 + src/go.sum | 2 + src/main.go | 21 ++++--- src/mod/acme/autorenew.go | 20 +++++-- src/mod/auth/router.go | 34 +++++++---- src/mod/dockerux/docker.go | 3 - src/mod/webserv/filemanager/filemanager.go | 6 +- src/reverseproxy.go | 40 ++++++++----- src/router.go | 62 +++++++++++++++++++- src/start.go | 2 +- src/upstreams.go | 2 +- src/web/components/access.html | 21 +++---- src/web/components/cert.html | 6 +- src/web/components/gan.html | 4 +- src/web/components/gandetails.html | 20 +++---- src/web/components/httprp.html | 26 ++++++++- src/web/components/networktools.html | 10 ++-- src/web/components/redirection.html | 7 ++- src/web/components/rproot.html | 5 +- src/web/components/rules.html | 23 ++------ src/web/components/status.html | 66 ++++++++++++++-------- src/web/components/streamprox.html | 10 ++-- src/web/components/utils.html | 4 +- src/web/components/vdir.html | 8 +-- src/web/components/webserv.html | 6 +- src/web/index.html | 1 + src/web/login.html | 64 +++++++++++++-------- src/web/reset.html | 46 +++++++++------ src/web/script/utils.js | 16 +++++- src/web/snippet/accessRuleEditor.html | 8 ++- src/web/snippet/acme.html | 51 +++++++++-------- src/web/snippet/advanceStatsOprs.html | 4 +- src/web/snippet/aliasEditor.html | 6 +- src/web/snippet/basicAuthEditor.html | 8 ++- src/web/snippet/configTools.html | 29 +++++----- src/web/snippet/customHeaders.html | 28 ++++----- src/web/snippet/hostAccessEditor.html | 8 +-- src/web/snippet/placeholder.html | 2 + src/web/snippet/upstreams.html | 18 +++--- src/web/tools/fs.html | 57 ++++++++++--------- src/web/tools/ipscan.html | 24 ++++++-- src/web/tools/mdns.html | 4 +- src/web/tools/sshconn.html | 4 +- 45 files changed, 535 insertions(+), 307 deletions(-) diff --git a/src/api.go b/src/api.go index 0d2f7c9..82dbfac 100644 --- a/src/api.go +++ b/src/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) diff --git a/src/cert.go b/src/cert.go index e4b285a..ce9f83b 100644 --- a/src/cert.go +++ b/src/cert.go @@ -182,27 +182,28 @@ func handleToggleTLSProxy(w http.ResponseWriter, r *http.Request) { sysdb.Read("settings", "usetls", ¤tTlsSetting) } - 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) } } diff --git a/src/go.mod b/src/go.mod index d665e55..57b4f13 100644 --- a/src/go.mod +++ b/src/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 diff --git a/src/go.sum b/src/go.sum index 5aaab55..0e2546d 100644 --- a/src/go.sum +++ b/src/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= diff --git a/src/main.go b/src/main.go index fc933a5..f3138bf 100644 --- a/src/main.go +++ b/src/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" @@ -60,7 +61,7 @@ var ( name = "Zoraxy" version = "3.1.0" nodeUUID = "generic" //System uuid, in uuidv4 format - development = true //Set this to false to use embedded web fs + development = false //Set this to false to use embedded web fs bootTime = time.Now().Unix() /* @@ -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) diff --git a/src/mod/acme/autorenew.go b/src/mod/acme/autorenew.go index a4134fc..73637cd 100644 --- a/src/mod/acme/autorenew.go +++ b/src/mod/acme/autorenew.go @@ -140,7 +140,7 @@ func (a *AutoRenewer) StopAutoRenewTicker() { // opr = setSelected -> Enter a list of file names (or matching rules) for auto renew // opr = setAuto -> Set to use auto detect certificates and renew func (a *AutoRenewer) HandleSetAutoRenewDomains(w http.ResponseWriter, r *http.Request) { - opr, err := utils.GetPara(r, "opr") + opr, err := utils.PostPara(r, "opr") if err != nil { utils.SendErrorResponse(w, "Operation not set") return @@ -170,6 +170,8 @@ func (a *AutoRenewer) HandleSetAutoRenewDomains(w http.ResponseWriter, r *http.R a.RenewerConfig.RenewAll = true a.saveRenewConfigToFile() utils.SendOK(w) + } else { + utils.SendErrorResponse(w, "invalid operation given") } } @@ -213,19 +215,22 @@ func (a *AutoRenewer) HandleRenewNow(w http.ResponseWriter, r *http.Request) { utils.SendJSONResponse(w, string(js)) } +// HandleAutoRenewEnable get and set the auto renew enable state func (a *AutoRenewer) HandleAutoRenewEnable(w http.ResponseWriter, r *http.Request) { - val, err := utils.PostPara(r, "enable") - if err != nil { + if r.Method == http.MethodGet { js, _ := json.Marshal(a.RenewerConfig.Enabled) utils.SendJSONResponse(w, string(js)) - } else { - if val == "true" { + } else if r.Method == http.MethodPost { + val, err := utils.PostBool(r, "enable") + if err != nil { + utils.SendErrorResponse(w, "invalid or empty enable state") + } + if val { //Check if the email is not empty if a.RenewerConfig.Email == "" { utils.SendErrorResponse(w, "Email is not set") return } - a.RenewerConfig.Enabled = true a.saveRenewConfigToFile() log.Println("[ACME] ACME auto renew enabled") @@ -236,7 +241,10 @@ func (a *AutoRenewer) HandleAutoRenewEnable(w http.ResponseWriter, r *http.Reque log.Println("[ACME] ACME auto renew disabled") a.StopAutoRenewTicker() } + } else { + http.Error(w, "405 - Method not allowed", http.StatusMethodNotAllowed) } + } func (a *AutoRenewer) HandleACMEEmail(w http.ResponseWriter, r *http.Request) { diff --git a/src/mod/auth/router.go b/src/mod/auth/router.go index 8fca7b4..85f9dae 100644 --- a/src/mod/auth/router.go +++ b/src/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 diff --git a/src/mod/dockerux/docker.go b/src/mod/dockerux/docker.go index 90aa0c7..9a3d5ad 100644 --- a/src/mod/dockerux/docker.go +++ b/src/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)) diff --git a/src/mod/webserv/filemanager/filemanager.go b/src/mod/webserv/filemanager/filemanager.go index 8847553..21b6bea 100644 --- a/src/mod/webserv/filemanager/filemanager.go +++ b/src/mod/webserv/filemanager/filemanager.go @@ -173,7 +173,7 @@ func (fm *FileManager) HandleDownload(w http.ResponseWriter, r *http.Request) { // HandleNewFolder creates a new folder in the specified directory func (fm *FileManager) HandleNewFolder(w http.ResponseWriter, r *http.Request) { // Parse the directory name from the request - dirName, err := utils.GetPara(r, "path") + dirName, err := utils.PostPara(r, "path") if err != nil { utils.SendErrorResponse(w, "invalid directory name") return @@ -268,13 +268,13 @@ func (fm *FileManager) HandleFileCopy(w http.ResponseWriter, r *http.Request) { func (fm *FileManager) HandleFileMove(w http.ResponseWriter, r *http.Request) { // Parse the source and destination paths from the request - srcPath, err := utils.GetPara(r, "srcpath") + srcPath, err := utils.PostPara(r, "srcpath") if err != nil { utils.SendErrorResponse(w, "invalid source path") return } - destPath, err := utils.GetPara(r, "destpath") + destPath, err := utils.PostPara(r, "destpath") if err != nil { utils.SendErrorResponse(w, "invalid destination path") return diff --git a/src/reverseproxy.go b/src/reverseproxy.go index cea0c50..369b7f1 100644 --- a/src/reverseproxy.go +++ b/src/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", ¤tEnabled) + err := sysdb.Read("settings", "listenP80", ¤tEnabled) 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", ¤tRedirectToHttps) + err := sysdb.Read("settings", "redirect", ¤tRedirectToHttps) 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) } } @@ -1086,13 +1100,13 @@ func HandleIncomingPortSet(w http.ResponseWriter, r *http.Request) { //List all the custom header defined in this proxy rule func HandleCustomHeaderList(w http.ResponseWriter, r *http.Request) { - epType, err := utils.PostPara(r, "type") + epType, err := utils.GetPara(r, "type") if err != nil { utils.SendErrorResponse(w, "endpoint type not defined") return } - domain, err := utils.PostPara(r, "domain") + domain, err := utils.GetPara(r, "domain") if err != nil { utils.SendErrorResponse(w, "domain or matching rule not defined") return diff --git a/src/router.go b/src/router.go index 3b64237..e324f00 100644 --- a/src/router.go +++ b/src/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,53 @@ 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, require trimming off the prefix slash for relative path + relativeFilepath = strings.TrimPrefix(relativeFilepath, "/") + content, err = webres.ReadFile(relativeFilepath) + if err != nil { + SystemWideLogger.Println("load embedded web file failed: ", err) + 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)) +} diff --git a/src/start.go b/src/start.go index 44a971e..cadac93 100644 --- a/src/start.go +++ b/src/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) diff --git a/src/upstreams.go b/src/upstreams.go index 3ab2eab..16df940 100644 --- a/src/upstreams.go +++ b/src/upstreams.go @@ -19,7 +19,7 @@ import ( // List upstreams from a endpoint func ReverseProxyUpstreamList(w http.ResponseWriter, r *http.Request) { - endpoint, err := utils.PostPara(r, "ep") + endpoint, err := utils.GetPara(r, "ep") if err != nil { utils.SendErrorResponse(w, "endpoint not defined") return diff --git a/src/web/components/access.html b/src/web/components/access.html index bb06e1d..5fd99cd 100644 --- a/src/web/components/access.html +++ b/src/web/components/access.html @@ -1000,7 +1000,7 @@

Quick Ban List

*/ function enableBlacklist() { var isChecked = $('#enableBlacklist').is(':checked'); - $.ajax({ + $.cjax({ type: 'POST', url: '/api/blacklist/enable', data: { enable: isChecked, id: currentEditingAccessRule}, @@ -1028,9 +1028,10 @@

Quick Ban List

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 @@

Quick Ban List

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 @@

Quick Ban List

} } - $.ajax({ + $.cjax({ url: "/api/blacklist/ip/add", type: "POST", data: {ip: targetIp.toLowerCase(), id: currentEditingAccessRule}, @@ -1119,7 +1120,7 @@

Quick Ban List

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 @@

Quick Ban List

*/ function enableWhitelist() { var isChecked = $('#enableWhitelist').is(':checked'); - $.ajax({ + $.cjax({ type: 'POST', url: '/api/whitelist/enable', data: { enable: isChecked , id: currentEditingAccessRule}, @@ -1165,7 +1166,7 @@

Quick Ban List

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 @@

Quick Ban List

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 @@

Quick Ban List

} } - $.ajax({ + $.cjax({ url: "/api/whitelist/ip/add", type: "POST", data: {ip: targetIp.toLowerCase(), "comment": remarks, id: currentEditingAccessRule}, @@ -1253,7 +1254,7 @@

Quick Ban List

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}, diff --git a/src/web/components/cert.html b/src/web/components/cert.html index 99fe7de..784203f 100644 --- a/src/web/components/cert.html +++ b/src/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", diff --git a/src/web/components/gan.html b/src/web/components/gan.html index 833b401..12402fa 100644 --- a/src/web/components/gan.html +++ b/src/web/components/gan.html @@ -87,7 +87,7 @@

} function addGANet() { - $.ajax({ + $.cjax({ url: "/api/gan/network/add", type: "POST", dataType: "json", @@ -191,7 +191,7 @@

//Remove the given GANet function removeGANet(netid){ if (confirm("Confirm remove Network " + netid + " PERMANENTLY ?")) - $.ajax({ + $.cjax({ url: "/api/gan/network/remove", type: "POST", dataType: "json", diff --git a/src/web/components/gandetails.html b/src/web/components/gandetails.html index 857033c..663109f 100644 --- a/src/web/components/gandetails.html +++ b/src/web/components/gandetails.html @@ -214,7 +214,7 @@

Add Controller as Member

//Get CIDR from selected range group var cidr = $(".iprange.active").attr("cidr"); - $.ajax({ + $.cjax({ url: "/api/gan/network/setRange", metohd: "POST", data:{ @@ -240,7 +240,7 @@

Add Controller as Member

if (object != undefined){ $(object).addClass("loading"); } - $.ajax({ + $.cjax({ url: "/api/gan/network/name", method: "POST", data: { @@ -287,7 +287,7 @@

Add Controller as Member

//Handle delete IP from memeber function deleteIpFromMemeber(memberid, ip){ - $.ajax({ + $.cjax({ url: "/api/gan/members/ip", metohd: "POST", data: { @@ -334,7 +334,7 @@

Add Controller as Member

return } - $.ajax({ + $.cjax({ url: "/api/gan/members/ip", metohd: "POST", data: { @@ -461,7 +461,7 @@

Add Controller as Member

$(".memberName").each(function(){ let addr = $(this).attr("addr"); let targetDOM = $(this); - $.ajax({ + $.cjax({ url: "/api/gan/members/name", method: "POST", data: { @@ -487,7 +487,7 @@

Add Controller as Member

let newname = prompt("Enter a easy manageable name for " + targetMemberAddr, ""); if (newname != null && newname.trim() != "") { - $.ajax({ + $.cjax({ url: "/api/gan/members/name", method: "POST", data: { @@ -553,7 +553,7 @@

Add Controller as Member

function handleMemberAuth(object){ let targetMemberAddr = $(object).attr("addr"); let isAuthed = object.checked; - $.ajax({ + $.cjax({ url: "/api/gan/members/authorize", method: "POST", data: { @@ -580,7 +580,7 @@

Add Controller as Member

function handleMemberDelete(addr){ if (confirm("Confirm delete member " + addr + " ?")){ - $.ajax({ + $.cjax({ url: "/api/gan/members/delete", method: "POST", data: { @@ -605,7 +605,7 @@

Add Controller as Member

$(".addControllerToNetworkBtn").addClass("disabled"); $(".addControllerToNetworkBtn").addClass("loading"); - $.ajax({ + $.cjax({ url: "/api/gan/network/join", method: "POST", data: { @@ -630,7 +630,7 @@

Add Controller as Member

$(".removeControllerFromNetworkBtn").addClass("disabled"); $(".removeControllerFromNetworkBtn").addClass("loading"); - $.ajax({ + $.cjax({ url: "/api/gan/network/leave", method: "POST", data: { diff --git a/src/web/components/httprp.html b/src/web/components/httprp.html index ba5466f..5eebc2c 100644 --- a/src/web/components/httprp.html +++ b/src/web/components/httprp.html @@ -400,7 +400,7 @@

HTTP Proxy

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,28 @@

HTTP Proxy

} }) } + + //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(); + }else{ + msgbox(data.error, false); + } + } + }) + } + } + /* button events */ function editBasicAuthCredentials(uuid){ @@ -474,7 +496,7 @@

HTTP Proxy

function handleProxyRuleToggle(object){ let endpointUUID = $(object).attr("eptuuid"); let isChecked = object.checked; - $.ajax({ + $.cjax({ url: "/api/proxy/toggle", data: { "ep": endpointUUID, diff --git a/src/web/components/networktools.html b/src/web/components/networktools.html index cc4ea71..dca3347 100644 --- a/src/web/components/networktools.html +++ b/src/web/components/networktools.html @@ -339,7 +339,7 @@

Network Interfaces

$("#wol_mac").parent().removeClass("error"); } - $.ajax({ + $.cjax({ url: wake_on_lan_API, type: "POST", data: { @@ -363,7 +363,7 @@

Network Interfaces

function delWoLAddr(mac, name) { if (confirm(`Confirm remove WoL record for ${name} (${mac}) ?`)){ - $.ajax({ + $.cjax({ url: wake_on_lan_API, type: "POST", data: { @@ -385,7 +385,7 @@

Network Interfaces

if (object != undefined){ $(object).addClass("loading").addClass("disabled"); } - $.ajax({ + $.cjax({ url: wake_on_lan_API, type: "POST", data: { @@ -594,7 +594,7 @@

Network Interfaces

initForwardProxyInfo(); function toggleForwadProxy(enabled){ - $.ajax({ + $.cjax({ url: "/api/tools/fwdproxy/enable", method: "POST", data: { @@ -620,7 +620,7 @@

Network Interfaces

$("#newPortNumber").parent().removeClass('error'); } - $.ajax({ + $.cjax({ url: "/api/tools/fwdproxy/port", method: "POST", data: { diff --git a/src/web/components/redirection.html b/src/web/components/redirection.html index c34af14..40bbeb4 100644 --- a/src/web/components/redirection.html +++ b/src/web/components/redirection.html @@ -116,7 +116,7 @@

Add Redirection Rule

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 @@

Add Redirection Rule

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 @@

Add Redirection Rule

//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){ diff --git a/src/web/components/rproot.html b/src/web/components/rproot.html index 56af8de..b1c3883 100644 --- a/src/web/components/rproot.html +++ b/src/web/components/rproot.html @@ -181,8 +181,9 @@

Default Site

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 @@

Default Site

} //Create the endpoint by calling add - $.ajax({ + $.cjax({ url: "/api/proxy/add", data: { "type": "root", diff --git a/src/web/components/rules.html b/src/web/components/rules.html index d175a98..2873660 100644 --- a/src/web/components/rules.html +++ b/src/web/components/rules.html @@ -212,8 +212,9 @@

New Proxy Rule

} //Create the endpoint by calling add - $.ajax({ + $.cjax({ url: "/api/proxy/add", + method: "POST", data: { type: "host", rootname: rootname, @@ -270,22 +271,6 @@

New Proxy Rule

} - //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 @@

New Proxy Rule

//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"){ diff --git a/src/web/components/status.html b/src/web/components/status.html index 3d187bb..0e0d19b 100644 --- a/src/web/components/status.html +++ b/src/web/components/status.html @@ -315,26 +315,39 @@

Statistic Overview

//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 @@

Statistic Overview

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 @@

Statistic Overview

//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 @@

Statistic Overview

//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,15 @@

Statistic Overview

}else{ $(".tlsEnabledOnly").addClass('disabled'); } - $.ajax({ + $.cjax({ url: "/api/cert/tls", + method: "POST", 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){ diff --git a/src/web/components/streamprox.html b/src/web/components/streamprox.html index 76be84f..64a9cdf 100644 --- a/src/web/components/streamprox.html +++ b/src/web/components/streamprox.html @@ -100,7 +100,7 @@

Add or Edit Stream Proxy

} // Send the AJAX POST request - $.ajax({ + $.cjax({ type: 'POST', url: '/api/streamprox/config/add', data: form.serialize(), @@ -285,7 +285,7 @@

Add or Edit Stream Proxy

} // Send the AJAX POST request - $.ajax({ + $.cjax({ type: 'POST', url: '/api/streamprox/config/edit', method: "POST", @@ -316,7 +316,7 @@

Add or Edit Stream Proxy

} function deleteTCPProxyConfig(configUUID){ - $.ajax({ + $.cjax({ url: "/api/streamprox/config/delete", method: "POST", data: {uuid: configUUID}, @@ -333,7 +333,7 @@

Add or Edit Stream Proxy

//Start a TCP proxy by their config UUID function startStreamProx(configUUID){ - $.ajax({ + $.cjax({ url: "/api/streamprox/config/start", method: "POST", data: {uuid: configUUID}, @@ -351,7 +351,7 @@

Add or Edit Stream Proxy

//Stop a TCP proxy by their config UUID function stopStreamProx(configUUID){ - $.ajax({ + $.cjax({ url: "/api/streamprox/config/stop", method: "POST", data: {uuid: configUUID}, diff --git a/src/web/components/utils.html b/src/web/components/utils.html index a99d6f0..138ccbe 100644 --- a/src/web/components/utils.html +++ b/src/web/components/utils.html @@ -233,7 +233,7 @@

const newPassword = document.getElementsByName('newPassword')[0].value; const confirmNewPassword = document.getElementsByName('confirmNewPassword')[0].value; - $.ajax({ + $.cjax({ type: "POST", url: "/api/auth/changePassword", data: { @@ -279,7 +279,7 @@

return; } - $.ajax({ + $.cjax({ type: "POST", url: "/api/tools/smtp/set", data: data, diff --git a/src/web/components/vdir.html b/src/web/components/vdir.html index 1749c13..8d8d7b2 100644 --- a/src/web/components/vdir.html +++ b/src/web/components/vdir.html @@ -190,7 +190,7 @@

New Virtual Directory Rule

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 @@

New Virtual Directory Rule

} //Create a virtual directory endpoint - $.ajax({ + $.cjax({ url: "/api/proxy/vdir/add", method: "POST", data: { @@ -295,7 +295,7 @@

New Virtual Directory Rule

epType = "root"; path = ""; } - $.ajax({ + $.cjax({ url: "/api/proxy/vdir/del", method: "POST", data: { @@ -384,7 +384,7 @@

New Virtual Directory Rule

//console.log(mathingPath, newDomain, requireTLS, skipValidation); - $.ajax({ + $.cjax({ url: "/api/proxy/vdir/edit", method: "POST", data: { diff --git a/src/web/components/webserv.html b/src/web/components/webserv.html index b67ff6a..ccc073e 100644 --- a/src/web/components/webserv.html +++ b/src/web/components/webserv.html @@ -164,7 +164,7 @@

$("#webserv_enableDirList").off("change").on("change", function(){ let enable = $(this)[0].checked; - $.ajax({ + $.cjax({ url: "/api/webserv/setDirList", method: "POST", data: {"enable": enable}, @@ -186,7 +186,7 @@

confirmBox("This setting might cause port conflict. Continue Anyway?", function(choice){ if (choice == true){ //Continue anyway - $.ajax({ + $.cjax({ url: "/api/webserv/setPort", method: "POST", data: {"port": newPort}, @@ -206,7 +206,7 @@

} }); }else{ - $.ajax({ + $.cjax({ url: "/api/webserv/setPort", method: "POST", data: {"port": newPort}, diff --git a/src/web/index.html b/src/web/index.html index a0cddf5..1ac34d7 100644 --- a/src/web/index.html +++ b/src/web/index.html @@ -5,6 +5,7 @@ + Control Panel | Zoraxy diff --git a/src/web/login.html b/src/web/login.html index 0bb9e8a..b779c8a 100644 --- a/src/web/login.html +++ b/src/web/login.html @@ -4,6 +4,7 @@ + Login | Zoraxy @@ -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(` ${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(` ${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'); }); } diff --git a/src/web/reset.html b/src/web/reset.html index 9a192ce..5f001c5 100644 --- a/src/web/reset.html +++ b/src/web/reset.html @@ -2,6 +2,7 @@ + @@ -255,25 +256,36 @@ } // Send POST request with input values as data - $.post('/api/account/new', { username: username, token: token, newpw: newPassword }) - .done(function(data) { - // Handle successful response - if (data.error != undefined){ - $("#errmsg").html(` ` + data.error); - $("#errmsg").show(); - }else{ - $("#errmsg").hide(); - $("#countdown").hide(); - $("#succmsg").show(); - setTimeout(function(){ - window.location.href = "/"; - }, 3000); + let csrfToken = document.getElementsByTagName("meta")["zoraxy.csrf.Token"].getAttribute("content"); + $.ajax({ + url: "/api/account/new", + method: "POST", + data: { + username: username, + token: token, + newpw: newPassword + }, + headers: { + "X-CSRF-Token": csrfToken, + }, + success: function(data){ + // Handle successful response + if (data.error != undefined){ + $("#errmsg").html(` ` + data.error); + $("#errmsg").show(); + }else{ + $("#errmsg").hide(); + $("#countdown").hide(); + $("#succmsg").show(); + setTimeout(function(){ + window.location.href = "/"; + }, 3000); + } + }, + error: function(){ + console.error(error); } }) - .fail(function(error) { - // Handle error response - console.error(error); - }); }); diff --git a/src/web/script/utils.js b/src/web/script/utils.js index 25238c6..289bd6b 100644 --- a/src/web/script/utils.js +++ b/src/web/script/utils.js @@ -26,4 +26,18 @@ Object.defineProperty(String.prototype, 'capitalize', { return this.charAt(0).toUpperCase() + this.slice(1); }, enumerable: false -}); \ No newline at end of file +}); + +//Add a new function to jquery for ajax override with csrf token injected +$.cjax = function(payload){ + let requireTokenMethod = ["POST", "PUT", "DELETE"];; + if (requireTokenMethod.includes(payload.method) || requireTokenMethod.includes(payload.type)){ + //csrf token is required + let csrfToken = document.getElementsByTagName("meta")["zoraxy.csrf.Token"].getAttribute("content"); + payload.headers = { + "X-CSRF-Token": csrfToken, + } + } + + $.ajax(payload); +} \ No newline at end of file diff --git a/src/web/snippet/accessRuleEditor.html b/src/web/snippet/accessRuleEditor.html index 604a855..90106bf 100644 --- a/src/web/snippet/accessRuleEditor.html +++ b/src/web/snippet/accessRuleEditor.html @@ -3,9 +3,11 @@ + +