Merge pull request #399 from tobychui/v3.1.4

V3.1.4
This commit is contained in:
Toby Chui 2024-11-24 14:47:53 +08:00 committed by GitHub
commit 0eb0696670
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
61 changed files with 25809 additions and 22851 deletions

View File

@ -33,6 +33,7 @@ A general purpose HTTP reverse proxy and forwarding tool. Now written in Go!
- Basic single-admin management mode
- External permission management system for easy system integration
- SMTP config for password reset
- Dark Theme Mode
## Downloads
@ -102,6 +103,8 @@ Usage of zoraxy:
Enable auto config upgrade if breaking change is detected (default true)
-docker
Run Zoraxy in docker compatibility mode
-earlyrenew int
Number of days to early renew a soon expiring certificate (days) (default 30)
-fastgeoip
Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)
-mdns
@ -119,7 +122,7 @@ Usage of zoraxy:
-webfm
Enable web file manager for static web server root folder (default true)
-webroot string
Static web server root folder. Only allow change in start parameters (default "./www")
Static web server root folder. Only allow chnage in start paramters (default "./www")
-ztauth string
ZeroTier authtoken for the local node
-ztport int

View File

@ -230,7 +230,17 @@ func handleCountryBlacklistAdd(w http.ResponseWriter, r *http.Request) {
return
}
rule.AddCountryCodeToBlackList(countryCode, comment)
//Check if the country code contains comma, if yes, split it
if strings.Contains(countryCode, ",") {
codes := strings.Split(countryCode, ",")
for _, code := range codes {
code = strings.TrimSpace(code)
rule.AddCountryCodeToBlackList(code, comment)
}
} else {
countryCode = strings.TrimSpace(countryCode)
rule.AddCountryCodeToBlackList(countryCode, comment)
}
utils.SendOK(w)
}
@ -254,7 +264,17 @@ func handleCountryBlacklistRemove(w http.ResponseWriter, r *http.Request) {
return
}
rule.RemoveCountryCodeFromBlackList(countryCode)
//Check if the country code contains comma, if yes, split it
if strings.Contains(countryCode, ",") {
codes := strings.Split(countryCode, ",")
for _, code := range codes {
code = strings.TrimSpace(code)
rule.RemoveCountryCodeFromBlackList(code)
}
} else {
countryCode = strings.TrimSpace(countryCode)
rule.RemoveCountryCodeFromBlackList(countryCode)
}
utils.SendOK(w)
}
@ -397,7 +417,17 @@ func handleCountryWhitelistAdd(w http.ResponseWriter, r *http.Request) {
p := bluemonday.StrictPolicy()
comment = p.Sanitize(comment)
rule.AddCountryCodeToWhitelist(countryCode, comment)
//Check if the country code contains comma, if yes, split it
if strings.Contains(countryCode, ",") {
codes := strings.Split(countryCode, ",")
for _, code := range codes {
code = strings.TrimSpace(code)
rule.AddCountryCodeToWhitelist(code, comment)
}
} else {
countryCode = strings.TrimSpace(countryCode)
rule.AddCountryCodeToWhitelist(countryCode, comment)
}
utils.SendOK(w)
}
@ -420,7 +450,17 @@ func handleCountryWhitelistRemove(w http.ResponseWriter, r *http.Request) {
return
}
rule.RemoveCountryCodeFromWhitelist(countryCode)
//Check if the country code contains comma, if yes, split it
if strings.Contains(countryCode, ",") {
codes := strings.Split(countryCode, ",")
for _, code := range codes {
code = strings.TrimSpace(code)
rule.RemoveCountryCodeFromWhitelist(code)
}
} else {
countryCode = strings.TrimSpace(countryCode)
rule.RemoveCountryCodeFromWhitelist(countryCode)
}
utils.SendOK(w)
}

View File

@ -41,6 +41,20 @@ func initACME() *acme.ACMEHandler {
return acme.NewACME("https://acme-v02.api.letsencrypt.org/directory", strconv.Itoa(port), sysdb, SystemWideLogger)
}
// Restart ACME handler and auto renewer
func restartACMEHandler() {
SystemWideLogger.Println("Restarting ACME handler")
//Clos the current handler and auto renewer
acmeHandler.Close()
acmeAutoRenewer.Close()
acmeDeregisterSpecialRoutingRule()
//Reinit the handler with a new random port
acmeHandler = initACME()
acmeRegisterSpecialRoutingRule()
}
// create the special routing rule for ACME
func acmeRegisterSpecialRoutingRule() {
SystemWideLogger.Println("Assigned temporary port:" + acmeHandler.Getport())
@ -82,6 +96,12 @@ func acmeRegisterSpecialRoutingRule() {
}
}
// remove the special routing rule for ACME
func acmeDeregisterSpecialRoutingRule() {
SystemWideLogger.Println("Removing ACME routing rule")
dynamicProxyRouter.RemoveRoutingRule("acme-autorenew")
}
// This function check if the renew setup is satisfied. If not, toggle them automatically
func AcmeCheckAndHandleRenewCertificate(w http.ResponseWriter, r *http.Request) {
isForceHttpsRedirectEnabledOriginally := false

View File

@ -8,6 +8,7 @@ import (
"imuslab.com/zoraxy/mod/acme/acmedns"
"imuslab.com/zoraxy/mod/acme/acmewizard"
"imuslab.com/zoraxy/mod/auth"
"imuslab.com/zoraxy/mod/dynamicproxy/domainsniff"
"imuslab.com/zoraxy/mod/ipscan"
"imuslab.com/zoraxy/mod/netstat"
"imuslab.com/zoraxy/mod/netutils"
@ -18,34 +19,11 @@ import (
API.go
This file contains all the API called by the web management interface
*/
var requireAuth = true
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)
},
})
//Register the standard web services urls
fs := http.FileServer(http.FS(webres))
if development {
fs = http.FileServer(http.Dir("web/"))
}
//Add a layer of middleware for advance control
advHandler := FSHandler(fs)
targetMux.Handle("/", advHandler)
//Authentication APIs
registerAuthAPIs(requireAuth, targetMux)
//Reverse proxy
// Register the APIs for HTTP proxy management functions
func RegisterHTTPProxyAPIs(authRouter *auth.RouterDef) {
/* Reverse Proxy Settings & Status */
authRouter.HandleFunc("/api/proxy/enable", ReverseProxyHandleOnOff)
authRouter.HandleFunc("/api/proxy/add", ReverseProxyHandleAddEndpoint)
authRouter.HandleFunc("/api/proxy/status", ReverseProxyStatus)
@ -56,24 +34,24 @@ func initAPIs(targetMux *http.ServeMux) {
authRouter.HandleFunc("/api/proxy/setAlias", ReverseProxyHandleAlias)
authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint)
authRouter.HandleFunc("/api/proxy/updateCredentials", UpdateProxyBasicAuthCredentials)
authRouter.HandleFunc("/api/proxy/tlscheck", HandleCheckSiteSupportTLS)
authRouter.HandleFunc("/api/proxy/tlscheck", domainsniff.HandleCheckSiteSupportTLS)
authRouter.HandleFunc("/api/proxy/setIncoming", HandleIncomingPortSet)
authRouter.HandleFunc("/api/proxy/useHttpsRedirect", HandleUpdateHttpsRedirect)
authRouter.HandleFunc("/api/proxy/listenPort80", HandleUpdatePort80Listener)
authRouter.HandleFunc("/api/proxy/requestIsProxied", HandleManagementProxyCheck)
authRouter.HandleFunc("/api/proxy/developmentMode", HandleDevelopmentModeChange)
//Reverse proxy upstream (load balance) APIs
/* Reverse proxy upstream (load balance) */
authRouter.HandleFunc("/api/proxy/upstream/list", ReverseProxyUpstreamList)
authRouter.HandleFunc("/api/proxy/upstream/add", ReverseProxyUpstreamAdd)
authRouter.HandleFunc("/api/proxy/upstream/setPriority", ReverseProxyUpstreamSetPriority)
authRouter.HandleFunc("/api/proxy/upstream/update", ReverseProxyUpstreamUpdate)
authRouter.HandleFunc("/api/proxy/upstream/remove", ReverseProxyUpstreamDelete)
//Reverse proxy virtual directory APIs
/* Reverse proxy virtual directory */
authRouter.HandleFunc("/api/proxy/vdir/list", ReverseProxyListVdir)
authRouter.HandleFunc("/api/proxy/vdir/add", ReverseProxyAddVdir)
authRouter.HandleFunc("/api/proxy/vdir/del", ReverseProxyDeleteVdir)
authRouter.HandleFunc("/api/proxy/vdir/edit", ReverseProxyEditVdir)
//Reverse proxy user define header apis
/* Reverse proxy user-defined header */
authRouter.HandleFunc("/api/proxy/header/list", HandleCustomHeaderList)
authRouter.HandleFunc("/api/proxy/header/add", HandleCustomHeaderAdd)
authRouter.HandleFunc("/api/proxy/header/remove", HandleCustomHeaderRemove)
@ -81,12 +59,14 @@ func initAPIs(targetMux *http.ServeMux) {
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
/* Reverse proxy auth related */
authRouter.HandleFunc("/api/proxy/auth/exceptions/list", ListProxyBasicAuthExceptionPaths)
authRouter.HandleFunc("/api/proxy/auth/exceptions/add", AddProxyBasicAuthExceptionPaths)
authRouter.HandleFunc("/api/proxy/auth/exceptions/delete", RemoveProxyBasicAuthExceptionPaths)
}
//TLS / SSL config
// Register the APIs for TLS / SSL certificate management functions
func RegisterTLSAPIs(authRouter *auth.RouterDef) {
authRouter.HandleFunc("/api/cert/tls", handleToggleTLSProxy)
authRouter.HandleFunc("/api/cert/tlsRequireLatest", handleSetTlsRequireLatest)
authRouter.HandleFunc("/api/cert/upload", handleCertUpload)
@ -95,8 +75,10 @@ func initAPIs(targetMux *http.ServeMux) {
authRouter.HandleFunc("/api/cert/listdomains", handleListDomains)
authRouter.HandleFunc("/api/cert/checkDefault", handleDefaultCertCheck)
authRouter.HandleFunc("/api/cert/delete", handleCertRemove)
}
//SSO and Oauth
// Register the APIs for SSO and Oauth functions, WIP
func RegisterSSOAPIs(authRouter *auth.RouterDef) {
authRouter.HandleFunc("/api/sso/status", ssoHandler.HandleSSOStatus)
authRouter.HandleFunc("/api/sso/enable", ssoHandler.HandleSSOEnable)
authRouter.HandleFunc("/api/sso/setPort", ssoHandler.HandlePortChange)
@ -110,48 +92,67 @@ func initAPIs(targetMux *http.ServeMux) {
authRouter.HandleFunc("/api/sso/user/add", ssoHandler.HandleAddUser)
authRouter.HandleFunc("/api/sso/user/edit", ssoHandler.HandleEditUser)
authRouter.HandleFunc("/api/sso/user/remove", ssoHandler.HandleRemoveUser)
}
//Redirection config
// Register the APIs for redirection rules management functions
func RegisterRedirectionAPIs(authRouter *auth.RouterDef) {
authRouter.HandleFunc("/api/redirect/list", handleListRedirectionRules)
authRouter.HandleFunc("/api/redirect/add", handleAddRedirectionRule)
authRouter.HandleFunc("/api/redirect/delete", handleDeleteRedirectionRule)
authRouter.HandleFunc("/api/redirect/regex", handleToggleRedirectRegexpSupport)
}
//Access Rules API
// Register the APIs for access rules management functions
func RegisterAccessRuleAPIs(authRouter *auth.RouterDef) {
/* Access Rules Settings & Status */
authRouter.HandleFunc("/api/access/list", handleListAccessRules)
authRouter.HandleFunc("/api/access/attach", handleAttachRuleToHost)
authRouter.HandleFunc("/api/access/create", handleCreateAccessRule)
authRouter.HandleFunc("/api/access/remove", handleRemoveAccessRule)
authRouter.HandleFunc("/api/access/update", handleUpadateAccessRule)
//Blacklist APIs
/* Blacklist */
authRouter.HandleFunc("/api/blacklist/list", handleListBlacklisted)
authRouter.HandleFunc("/api/blacklist/country/add", handleCountryBlacklistAdd)
authRouter.HandleFunc("/api/blacklist/country/remove", handleCountryBlacklistRemove)
authRouter.HandleFunc("/api/blacklist/ip/add", handleIpBlacklistAdd)
authRouter.HandleFunc("/api/blacklist/ip/remove", handleIpBlacklistRemove)
authRouter.HandleFunc("/api/blacklist/enable", handleBlacklistEnable)
//Whitelist APIs
/* Whitelist */
authRouter.HandleFunc("/api/whitelist/list", handleListWhitelisted)
authRouter.HandleFunc("/api/whitelist/country/add", handleCountryWhitelistAdd)
authRouter.HandleFunc("/api/whitelist/country/remove", handleCountryWhitelistRemove)
authRouter.HandleFunc("/api/whitelist/ip/add", handleIpWhitelistAdd)
authRouter.HandleFunc("/api/whitelist/ip/remove", handleIpWhitelistRemove)
authRouter.HandleFunc("/api/whitelist/enable", handleWhitelistEnable)
}
//Path Blocker APIs
// Register the APIs for path blocking rules management functions, WIP
func RegisterPathRuleAPIs(authRouter *auth.RouterDef) {
authRouter.HandleFunc("/api/pathrule/add", pathRuleHandler.HandleAddBlockingPath)
authRouter.HandleFunc("/api/pathrule/list", pathRuleHandler.HandleListBlockingPath)
authRouter.HandleFunc("/api/pathrule/remove", pathRuleHandler.HandleRemoveBlockingPath)
}
//Statistic & uptime monitoring API
// Register the APIs statistic anlysis and uptime monitoring functions
func RegisterStatisticalAPIs(authRouter *auth.RouterDef) {
/* Traffic Summary */
authRouter.HandleFunc("/api/stats/summary", statisticCollector.HandleTodayStatLoad)
authRouter.HandleFunc("/api/stats/countries", HandleCountryDistrSummary)
authRouter.HandleFunc("/api/stats/netstat", netstatBuffers.HandleGetNetworkInterfaceStats)
authRouter.HandleFunc("/api/stats/netstatgraph", netstatBuffers.HandleGetBufferedNetworkInterfaceStats)
authRouter.HandleFunc("/api/stats/listnic", netstat.HandleListNetworkInterfaces)
/* Zoraxy Analytic */
authRouter.HandleFunc("/api/analytic/list", AnalyticLoader.HandleSummaryList)
authRouter.HandleFunc("/api/analytic/load", AnalyticLoader.HandleLoadTargetDaySummary)
authRouter.HandleFunc("/api/analytic/loadRange", AnalyticLoader.HandleLoadTargetRangeSummary)
authRouter.HandleFunc("/api/analytic/exportRange", AnalyticLoader.HandleRangeExport)
authRouter.HandleFunc("/api/analytic/resetRange", AnalyticLoader.HandleRangeReset)
/* UpTime Monitor */
authRouter.HandleFunc("/api/utm/list", HandleUptimeMonitorListing)
}
//Global Area Network APIs
// Register the APIs for Global Area Network management functions, Will be moving to plugin soon
func RegisterGANAPIs(authRouter *auth.RouterDef) {
authRouter.HandleFunc("/api/gan/network/info", ganManager.HandleGetNodeID)
authRouter.HandleFunc("/api/gan/network/add", ganManager.HandleAddNetwork)
authRouter.HandleFunc("/api/gan/network/remove", ganManager.HandleRemoveNetwork)
@ -166,8 +167,10 @@ func initAPIs(targetMux *http.ServeMux) {
authRouter.HandleFunc("/api/gan/members/name", ganManager.HandleMemberNaming)
authRouter.HandleFunc("/api/gan/members/authorize", ganManager.HandleMemberAuthorization)
authRouter.HandleFunc("/api/gan/members/delete", ganManager.HandleMemberDelete)
}
//Stream (TCP / UDP) Proxy
// Register the APIs for Stream (TCP / UDP) Proxy management functions
func RegisterStreamProxyAPIs(authRouter *auth.RouterDef) {
authRouter.HandleFunc("/api/streamprox/config/add", streamProxyManager.HandleAddProxyConfig)
authRouter.HandleFunc("/api/streamprox/config/edit", streamProxyManager.HandleEditProxyConfigs)
authRouter.HandleFunc("/api/streamprox/config/list", streamProxyManager.HandleListConfigs)
@ -175,19 +178,57 @@ func initAPIs(targetMux *http.ServeMux) {
authRouter.HandleFunc("/api/streamprox/config/stop", streamProxyManager.HandleStopProxy)
authRouter.HandleFunc("/api/streamprox/config/delete", streamProxyManager.HandleRemoveProxy)
authRouter.HandleFunc("/api/streamprox/config/status", streamProxyManager.HandleGetProxyStatus)
}
//mDNS APIs
// Register the APIs for mDNS service management functions
func RegisterMDNSAPIs(authRouter *auth.RouterDef) {
authRouter.HandleFunc("/api/mdns/list", HandleMdnsListing)
authRouter.HandleFunc("/api/mdns/discover", HandleMdnsScanning)
}
//Zoraxy Analytic
authRouter.HandleFunc("/api/analytic/list", AnalyticLoader.HandleSummaryList)
authRouter.HandleFunc("/api/analytic/load", AnalyticLoader.HandleLoadTargetDaySummary)
authRouter.HandleFunc("/api/analytic/loadRange", AnalyticLoader.HandleLoadTargetRangeSummary)
authRouter.HandleFunc("/api/analytic/exportRange", AnalyticLoader.HandleRangeExport)
authRouter.HandleFunc("/api/analytic/resetRange", AnalyticLoader.HandleRangeReset)
// Register the APIs for ACME and Auto Renewer management functions
func RegisterACMEAndAutoRenewerAPIs(authRouter *auth.RouterDef) {
/* ACME Core */
authRouter.HandleFunc("/api/acme/listExpiredDomains", acmeHandler.HandleGetExpiredDomains)
authRouter.HandleFunc("/api/acme/obtainCert", AcmeCheckAndHandleRenewCertificate)
/* Auto Renewer */
authRouter.HandleFunc("/api/acme/autoRenew/enable", acmeAutoRenewer.HandleAutoRenewEnable)
authRouter.HandleFunc("/api/acme/autoRenew/ca", HandleACMEPreferredCA)
authRouter.HandleFunc("/api/acme/autoRenew/email", acmeAutoRenewer.HandleACMEEmail)
authRouter.HandleFunc("/api/acme/autoRenew/setDomains", acmeAutoRenewer.HandleSetAutoRenewDomains)
authRouter.HandleFunc("/api/acme/autoRenew/setEAB", acmeAutoRenewer.HanldeSetEAB)
authRouter.HandleFunc("/api/acme/autoRenew/setDNS", acmeAutoRenewer.HandleSetDNS)
authRouter.HandleFunc("/api/acme/autoRenew/listDomains", acmeAutoRenewer.HandleLoadAutoRenewDomains)
authRouter.HandleFunc("/api/acme/autoRenew/renewPolicy", acmeAutoRenewer.HandleRenewPolicy)
authRouter.HandleFunc("/api/acme/autoRenew/renewNow", acmeAutoRenewer.HandleRenewNow)
authRouter.HandleFunc("/api/acme/dns/providers", acmedns.HandleServeProvidersJson)
/* ACME Wizard */
authRouter.HandleFunc("/api/acme/wizard", acmewizard.HandleGuidedStepCheck)
}
//Network utilities
// Register the APIs for Static Web Server management functions
func RegisterStaticWebServerAPIs(authRouter *auth.RouterDef) {
/* Static Web Server Controls */
authRouter.HandleFunc("/api/webserv/status", staticWebServer.HandleGetStatus)
authRouter.HandleFunc("/api/webserv/start", staticWebServer.HandleStartServer)
authRouter.HandleFunc("/api/webserv/stop", staticWebServer.HandleStopServer)
authRouter.HandleFunc("/api/webserv/setPort", HandleStaticWebServerPortChange)
authRouter.HandleFunc("/api/webserv/setDirList", staticWebServer.SetEnableDirectoryListing)
/* File Manager */
if *allowWebFileManager {
authRouter.HandleFunc("/api/fs/list", staticWebServer.FileManager.HandleList)
authRouter.HandleFunc("/api/fs/upload", staticWebServer.FileManager.HandleUpload)
authRouter.HandleFunc("/api/fs/download", staticWebServer.FileManager.HandleDownload)
authRouter.HandleFunc("/api/fs/newFolder", staticWebServer.FileManager.HandleNewFolder)
authRouter.HandleFunc("/api/fs/copy", staticWebServer.FileManager.HandleFileCopy)
authRouter.HandleFunc("/api/fs/move", staticWebServer.FileManager.HandleFileMove)
authRouter.HandleFunc("/api/fs/properties", staticWebServer.FileManager.HandleFileProperties)
authRouter.HandleFunc("/api/fs/del", staticWebServer.FileManager.HandleFileDelete)
}
}
// Register the APIs for Network Utilities functions
func RegisterNetworkUtilsAPIs(authRouter *auth.RouterDef) {
authRouter.HandleFunc("/api/tools/ipscan", ipscan.HandleIpScan)
authRouter.HandleFunc("/api/tools/portscan", ipscan.HandleScanPort)
authRouter.HandleFunc("/api/tools/traceroute", netutils.HandleTraceRoute)
@ -202,66 +243,10 @@ func initAPIs(targetMux *http.ServeMux) {
authRouter.HandleFunc("/api/tools/smtp/test", HandleTestEmailSend)
authRouter.HandleFunc("/api/tools/fwdproxy/enable", forwardProxy.HandleToogle)
authRouter.HandleFunc("/api/tools/fwdproxy/port", forwardProxy.HandlePort)
//Account Reset
targetMux.HandleFunc("/api/account/reset", HandleAdminAccountResetEmail)
targetMux.HandleFunc("/api/account/new", HandleNewPasswordSetup)
//ACME & Auto Renewer
authRouter.HandleFunc("/api/acme/listExpiredDomains", acmeHandler.HandleGetExpiredDomains)
authRouter.HandleFunc("/api/acme/obtainCert", AcmeCheckAndHandleRenewCertificate)
authRouter.HandleFunc("/api/acme/autoRenew/enable", acmeAutoRenewer.HandleAutoRenewEnable)
authRouter.HandleFunc("/api/acme/autoRenew/ca", HandleACMEPreferredCA)
authRouter.HandleFunc("/api/acme/autoRenew/email", acmeAutoRenewer.HandleACMEEmail)
authRouter.HandleFunc("/api/acme/autoRenew/setDomains", acmeAutoRenewer.HandleSetAutoRenewDomains)
authRouter.HandleFunc("/api/acme/autoRenew/setEAB", acmeAutoRenewer.HanldeSetEAB)
authRouter.HandleFunc("/api/acme/autoRenew/setDNS", acmeAutoRenewer.HanldeSetDNS)
authRouter.HandleFunc("/api/acme/autoRenew/listDomains", acmeAutoRenewer.HandleLoadAutoRenewDomains)
authRouter.HandleFunc("/api/acme/autoRenew/renewPolicy", acmeAutoRenewer.HandleRenewPolicy)
authRouter.HandleFunc("/api/acme/autoRenew/renewNow", acmeAutoRenewer.HandleRenewNow)
authRouter.HandleFunc("/api/acme/dns/providers", acmedns.HandleServeProvidersJson)
authRouter.HandleFunc("/api/acme/wizard", acmewizard.HandleGuidedStepCheck) //ACME Wizard
//Static Web Server
authRouter.HandleFunc("/api/webserv/status", staticWebServer.HandleGetStatus)
authRouter.HandleFunc("/api/webserv/start", staticWebServer.HandleStartServer)
authRouter.HandleFunc("/api/webserv/stop", staticWebServer.HandleStopServer)
authRouter.HandleFunc("/api/webserv/setPort", HandleStaticWebServerPortChange)
authRouter.HandleFunc("/api/webserv/setDirList", staticWebServer.SetEnableDirectoryListing)
if *allowWebFileManager {
//Web Directory Manager file operation functions
authRouter.HandleFunc("/api/fs/list", staticWebServer.FileManager.HandleList)
authRouter.HandleFunc("/api/fs/upload", staticWebServer.FileManager.HandleUpload)
authRouter.HandleFunc("/api/fs/download", staticWebServer.FileManager.HandleDownload)
authRouter.HandleFunc("/api/fs/newFolder", staticWebServer.FileManager.HandleNewFolder)
authRouter.HandleFunc("/api/fs/copy", staticWebServer.FileManager.HandleFileCopy)
authRouter.HandleFunc("/api/fs/move", staticWebServer.FileManager.HandleFileMove)
authRouter.HandleFunc("/api/fs/properties", staticWebServer.FileManager.HandleFileProperties)
authRouter.HandleFunc("/api/fs/del", staticWebServer.FileManager.HandleFileDelete)
}
//Docker UX Optimizations
authRouter.HandleFunc("/api/docker/available", DockerUXOptimizer.HandleDockerAvailable)
authRouter.HandleFunc("/api/docker/containers", DockerUXOptimizer.HandleDockerContainersList)
//Others
targetMux.HandleFunc("/api/info/x", HandleZoraxyInfo)
authRouter.HandleFunc("/api/info/geoip", HandleGeoIpLookup)
authRouter.HandleFunc("/api/conf/export", ExportConfigAsZip)
authRouter.HandleFunc("/api/conf/import", ImportConfigFromZip)
authRouter.HandleFunc("/api/log/list", LogViewer.HandleListLog)
authRouter.HandleFunc("/api/log/read", LogViewer.HandleReadLog)
//Debug
authRouter.HandleFunc("/api/info/pprof", pprof.Index)
//If you got APIs to add, append them here
}
// Function to renders Auth related APIs
func registerAuthAPIs(requireAuth bool, targetMux *http.ServeMux) {
//Auth APIs
// Register the APIs for Auth functions, due to scoping issue some functions are defined here
func RegisterAuthAPIs(requireAuth bool, targetMux *http.ServeMux) {
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) {
@ -277,21 +262,17 @@ func registerAuthAPIs(requireAuth bool, targetMux *http.ServeMux) {
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}
js, _ := json.Marshal(username)
utils.SendJSONResponse(w, string(js))
})
targetMux.HandleFunc("/api/auth/userCount", func(w http.ResponseWriter, r *http.Request) {
uc := authAgent.GetUserCounts()
js, _ := json.Marshal(uc)
js, _ := json.Marshal(authAgent.GetUserCounts())
utils.SendJSONResponse(w, string(js))
})
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) {
})
authAgent.HandleRegisterWithoutEmail(w, r, func(username, reserved string) {})
} else {
//This function is disabled
utils.SendErrorResponse(w, "Root management account already exists")
@ -332,5 +313,60 @@ func registerAuthAPIs(requireAuth bool, targetMux *http.ServeMux) {
authAgent.UnregisterUser(username)
authAgent.CreateUserAccount(username, newPassword, "")
})
}
/* Register all the APIs */
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)
},
})
//Register the standard web services urls
fs := http.FileServer(http.FS(webres))
if DEVELOPMENT_BUILD {
fs = http.FileServer(http.Dir("web/"))
}
//Add a layer of middleware for advance control
advHandler := FSHandler(fs)
targetMux.Handle("/", advHandler)
//Register the APIs
RegisterAuthAPIs(requireAuth, targetMux)
RegisterHTTPProxyAPIs(authRouter)
RegisterTLSAPIs(authRouter)
//RegisterSSOAPIs(authRouter)
RegisterRedirectionAPIs(authRouter)
RegisterAccessRuleAPIs(authRouter)
RegisterPathRuleAPIs(authRouter)
RegisterStatisticalAPIs(authRouter)
RegisterGANAPIs(authRouter)
RegisterStreamProxyAPIs(authRouter)
RegisterMDNSAPIs(authRouter)
RegisterNetworkUtilsAPIs(authRouter)
RegisterACMEAndAutoRenewerAPIs(authRouter)
RegisterStaticWebServerAPIs(authRouter)
//Account Reset
targetMux.HandleFunc("/api/account/reset", HandleAdminAccountResetEmail)
targetMux.HandleFunc("/api/account/new", HandleNewPasswordSetup)
//Docker UX Optimizations
authRouter.HandleFunc("/api/docker/available", DockerUXOptimizer.HandleDockerAvailable)
authRouter.HandleFunc("/api/docker/containers", DockerUXOptimizer.HandleDockerContainersList)
//Others
targetMux.HandleFunc("/api/info/x", HandleZoraxyInfo)
authRouter.HandleFunc("/api/info/geoip", HandleGeoIpLookup)
authRouter.HandleFunc("/api/conf/export", ExportConfigAsZip)
authRouter.HandleFunc("/api/conf/import", ImportConfigFromZip)
authRouter.HandleFunc("/api/log/list", LogViewer.HandleListLog)
authRouter.HandleFunc("/api/log/read", LogViewer.HandleReadLog)
//Debug
authRouter.HandleFunc("/api/info/pprof", pprof.Index)
}

View File

@ -177,7 +177,10 @@ func handleListDomains(w http.ResponseWriter, r *http.Request) {
// Handle front-end toggling TLS mode
func handleToggleTLSProxy(w http.ResponseWriter, r *http.Request) {
currentTlsSetting := false
currentTlsSetting := true //Default to true
if dynamicProxyRouter.Option != nil {
currentTlsSetting = dynamicProxyRouter.Option.UseTls
}
if sysdb.KeyExists("settings", "usetls") {
sysdb.Read("settings", "usetls", &currentTlsSetting)
}

138
src/def.go Normal file
View File

@ -0,0 +1,138 @@
package main
/*
Type and flag definations
This file contains all the type and flag definations
Author: tobychui
*/
import (
"embed"
"flag"
"net/http"
"time"
"imuslab.com/zoraxy/mod/access"
"imuslab.com/zoraxy/mod/acme"
"imuslab.com/zoraxy/mod/auth"
"imuslab.com/zoraxy/mod/auth/sso"
"imuslab.com/zoraxy/mod/database"
"imuslab.com/zoraxy/mod/dockerux"
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
"imuslab.com/zoraxy/mod/email"
"imuslab.com/zoraxy/mod/forwardproxy"
"imuslab.com/zoraxy/mod/ganserv"
"imuslab.com/zoraxy/mod/geodb"
"imuslab.com/zoraxy/mod/info/logger"
"imuslab.com/zoraxy/mod/info/logviewer"
"imuslab.com/zoraxy/mod/mdns"
"imuslab.com/zoraxy/mod/netstat"
"imuslab.com/zoraxy/mod/pathrule"
"imuslab.com/zoraxy/mod/sshprox"
"imuslab.com/zoraxy/mod/statistic"
"imuslab.com/zoraxy/mod/statistic/analytic"
"imuslab.com/zoraxy/mod/streamproxy"
"imuslab.com/zoraxy/mod/tlscert"
"imuslab.com/zoraxy/mod/uptime"
"imuslab.com/zoraxy/mod/webserv"
)
const (
/* Build Constants */
SYSTEM_NAME = "Zoraxy"
SYSTEM_VERSION = "3.1.4"
DEVELOPMENT_BUILD = false /* Development: Set to false to use embedded web fs */
/* System Constants */
DATABASE_PATH = "sys.db"
TMP_FOLDER = "./tmp"
WEBSERV_DEFAULT_PORT = 5487
MDNS_HOSTNAME_PREFIX = "zoraxy_" /* Follow by node UUID */
MDNS_IDENTIFY_DEVICE_TYPE = "Network Gateway"
MDNS_IDENTIFY_DOMAIN = "zoraxy.aroz.org"
MDNS_IDENTIFY_VENDOR = "imuslab.com"
MDNS_SCAN_TIMEOUT = 30 /* Seconds */
MDNS_SCAN_UPDATE_INTERVAL = 15 /* Minutes */
GEODB_CACHE_CLEAR_INTERVAL = 15 /* Minutes */
ACME_AUTORENEW_CONFIG_PATH = "./conf/acme_conf.json"
CSRF_COOKIENAME = "zoraxy_csrf"
LOG_PREFIX = "zr"
LOG_FOLDER = "./log"
LOG_EXTENSION = ".log"
/* Configuration Folder Storage Path Constants */
CONF_HTTP_PROXY = "./conf/proxy"
CONF_STREAM_PROXY = "./conf/streamproxy"
CONF_CERT_STORE = "./conf/certs"
CONF_REDIRECTION = "./conf/redirect"
CONF_ACCESS_RULE = "./conf/access"
CONF_PATH_RULE = "./conf/rules/pathrules"
)
/* System Startup Flags */
var (
webUIPort = flag.String("port", ":8000", "Management web interface listening port")
noauth = flag.Bool("noauth", false, "Disable authentication for management interface")
showver = flag.Bool("version", false, "Show version of this server")
allowSshLoopback = flag.Bool("sshlb", false, "Allow loopback web ssh connection (DANGER)")
allowMdnsScanning = flag.Bool("mdns", true, "Enable mDNS scanner and transponder")
mdnsName = flag.String("mdnsname", "", "mDNS name, leave empty to use default (zoraxy_{node-uuid}.local)")
ztAuthToken = flag.String("ztauth", "", "ZeroTier authtoken for the local node")
ztAPIPort = flag.Int("ztport", 9993, "ZeroTier controller API port")
runningInDocker = flag.Bool("docker", false, "Run Zoraxy in docker compatibility mode")
acmeAutoRenewInterval = flag.Int("autorenew", 86400, "ACME auto TLS/SSL certificate renew check interval (seconds)")
acmeCertAutoRenewDays = flag.Int("earlyrenew", 30, "Number of days to early renew a soon expiring certificate (days)")
enableHighSpeedGeoIPLookup = flag.Bool("fastgeoip", false, "Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)")
staticWebServerRoot = flag.String("webroot", "./www", "Static web server root folder. Only allow chnage in start paramters")
allowWebFileManager = flag.Bool("webfm", true, "Enable web file manager for static web server root folder")
enableAutoUpdate = flag.Bool("cfgupgrade", true, "Enable auto config upgrade if breaking change is detected")
)
/* Global Variables and Handlers */
var (
nodeUUID = "generic" //System uuid in uuidv4 format, load from database on startup
bootTime = time.Now().Unix()
requireAuth = true //Require authentication for webmin panel, override from flag
/*
Binary Embedding File System
*/
//go:embed web/*
webres embed.FS
/*
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
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
accessController *access.Controller //Access controller, handle black list and white list
netstatBuffers *netstat.NetStatBuffers //Realtime graph buffers
statisticCollector *statistic.Collector //Collecting statistic from visitors
uptimeMonitor *uptime.Monitor //Uptime monitor service worker
mdnsScanner *mdns.MDNSHost //mDNS discovery services
ganManager *ganserv.NetworkManager //Global Area Network Manager
webSshManager *sshprox.Manager //Web SSH connection service
streamProxyManager *streamproxy.Manager //Stream Proxy Manager for TCP / UDP forwarding
acmeHandler *acme.ACMEHandler //Handler for ACME Certificate renew
acmeAutoRenewer *acme.AutoRenewer //Handler for ACME auto renew ticking
staticWebServer *webserv.WebServer //Static web server for hosting simple stuffs
forwardProxy *forwardproxy.Handler //HTTP Forward proxy, basically VPN for web browser
loadBalancer *loadbalance.RouteManager //Global scope loadbalancer, store the state of the lb routing
ssoHandler *sso.SSOHandler //Single Sign On handler
//Helper modules
EmailSender *email.Sender //Email sender that handle email sending
AnalyticLoader *analytic.DataLoader //Data loader for Zoraxy Analytic
DockerUXOptimizer *dockerux.UXOptimizer //Docker user experience optimizer, community contribution only
SystemWideLogger *logger.Logger //Logger for Zoraxy
LogViewer *logviewer.Viewer //Log viewer HTTP handlers
)

View File

@ -1,7 +1,36 @@
package main
/*
______
|___ /
/ / ___ _ __ __ ___ ___ _
/ / / _ \| '__/ _` \ \/ / | | |
/ /_| (_) | | | (_| |> <| |_| |
/_____\___/|_| \__,_/_/\_\\__, |
__/ |
|___/
Zoraxy - A general purpose HTTP reverse proxy and forwarding tool
Author: tobychui
License: AGPLv3
--------------------------------------------
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, version 3 of the License or any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"embed"
"flag"
"fmt"
"log"
@ -13,100 +42,12 @@ import (
"github.com/google/uuid"
"github.com/gorilla/csrf"
"imuslab.com/zoraxy/mod/access"
"imuslab.com/zoraxy/mod/acme"
"imuslab.com/zoraxy/mod/auth"
"imuslab.com/zoraxy/mod/auth/sso"
"imuslab.com/zoraxy/mod/database"
"imuslab.com/zoraxy/mod/dockerux"
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
"imuslab.com/zoraxy/mod/email"
"imuslab.com/zoraxy/mod/forwardproxy"
"imuslab.com/zoraxy/mod/ganserv"
"imuslab.com/zoraxy/mod/geodb"
"imuslab.com/zoraxy/mod/info/logger"
"imuslab.com/zoraxy/mod/info/logviewer"
"imuslab.com/zoraxy/mod/mdns"
"imuslab.com/zoraxy/mod/netstat"
"imuslab.com/zoraxy/mod/pathrule"
"imuslab.com/zoraxy/mod/sshprox"
"imuslab.com/zoraxy/mod/statistic"
"imuslab.com/zoraxy/mod/statistic/analytic"
"imuslab.com/zoraxy/mod/streamproxy"
"imuslab.com/zoraxy/mod/tlscert"
"imuslab.com/zoraxy/mod/update"
"imuslab.com/zoraxy/mod/uptime"
"imuslab.com/zoraxy/mod/utils"
"imuslab.com/zoraxy/mod/webserv"
)
// General flags
var webUIPort = flag.String("port", ":8000", "Management web interface listening port")
var noauth = flag.Bool("noauth", false, "Disable authentication for management interface")
var showver = flag.Bool("version", false, "Show version of this server")
var allowSshLoopback = flag.Bool("sshlb", false, "Allow loopback web ssh connection (DANGER)")
var allowMdnsScanning = flag.Bool("mdns", true, "Enable mDNS scanner and transponder")
var mdnsName = flag.String("mdnsname", "", "mDNS name, leave empty to use default (zoraxy_{node-uuid}.local)")
var ztAuthToken = flag.String("ztauth", "", "ZeroTier authtoken for the local node")
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")
var enableAutoUpdate = flag.Bool("cfgupgrade", true, "Enable auto config upgrade if breaking change is detected")
var (
name = "Zoraxy"
version = "3.1.3"
nodeUUID = "generic" //System uuid, in uuidv4 format
development = false //Set this to false to use embedded web fs
bootTime = time.Now().Unix()
/*
Binary Embedding File System
*/
//go:embed web/*
webres embed.FS
/*
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
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
accessController *access.Controller //Access controller, handle black list and white list
netstatBuffers *netstat.NetStatBuffers //Realtime graph buffers
statisticCollector *statistic.Collector //Collecting statistic from visitors
uptimeMonitor *uptime.Monitor //Uptime monitor service worker
mdnsScanner *mdns.MDNSHost //mDNS discovery services
ganManager *ganserv.NetworkManager //Global Area Network Manager
webSshManager *sshprox.Manager //Web SSH connection service
streamProxyManager *streamproxy.Manager //Stream Proxy Manager for TCP / UDP forwarding
acmeHandler *acme.ACMEHandler //Handler for ACME Certificate renew
acmeAutoRenewer *acme.AutoRenewer //Handler for ACME auto renew ticking
staticWebServer *webserv.WebServer //Static web server for hosting simple stuffs
forwardProxy *forwardproxy.Handler //HTTP Forward proxy, basically VPN for web browser
loadBalancer *loadbalance.RouteManager //Global scope loadbalancer, store the state of the lb routing
ssoHandler *sso.SSOHandler //Single Sign On handler
//Helper modules
EmailSender *email.Sender //Email sender that handle email sending
AnalyticLoader *analytic.DataLoader //Data loader for Zoraxy Analytic
DockerUXOptimizer *dockerux.UXOptimizer //Docker user experience optimizer, community contribution only
SystemWideLogger *logger.Logger //Logger for Zoraxy
LogViewer *logviewer.Viewer
)
// Kill signal handler. Do something before the system the core terminate.
/* SIGTERM handler, do shutdown sequences before closing */
func SetupCloseHandler() {
c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
@ -118,9 +59,7 @@ func SetupCloseHandler() {
}
func ShutdownSeq() {
SystemWideLogger.Println("Shutting down " + name)
//SystemWideLogger.Println("Closing GeoDB")
//geodbStore.Close()
SystemWideLogger.Println("Shutting down " + SYSTEM_NAME)
SystemWideLogger.Println("Closing Netstats Listener")
netstatBuffers.Close()
SystemWideLogger.Println("Closing Statistic Collector")
@ -152,7 +91,7 @@ func main() {
//Parse startup flags
flag.Parse()
if *showver {
fmt.Println(name + " - Version " + version)
fmt.Println(SYSTEM_NAME + " - Version " + SYSTEM_VERSION)
os.Exit(0)
}
@ -163,7 +102,7 @@ func main() {
if *enableAutoUpdate {
fmt.Println("Checking required config update")
update.RunConfigUpdate(0, update.GetVersionIntFromVersionNumber(version))
update.RunConfigUpdate(0, update.GetVersionIntFromVersionNumber(SYSTEM_VERSION))
}
SetupCloseHandler()
@ -185,7 +124,7 @@ func main() {
webminPanelMux = http.NewServeMux()
csrfMiddleware = csrf.Protect(
[]byte(nodeUUID),
csrf.CookieName("zoraxy-csrf"),
csrf.CookieName(CSRF_COOKIENAME),
csrf.Secure(false),
csrf.Path("/"),
csrf.SameSite(csrf.SameSiteLaxMode),
@ -208,11 +147,10 @@ func main() {
//Start the finalize sequences
finalSequence()
SystemWideLogger.Println("Zoraxy started. Visit control panel at http://localhost" + *webUIPort)
SystemWideLogger.Println(SYSTEM_NAME + " started. Visit control panel at http://localhost" + *webUIPort)
err = http.ListenAndServe(*webUIPort, csrfMiddleware(webminPanelMux))
if err != nil {
log.Fatal(err)
}
}

View File

@ -86,6 +86,13 @@ func (a *ACMEHandler) Logf(message string, err error) {
a.Logger.PrintAndLog("ACME", message, err)
}
// Close closes the ACMEHandler.
// ACME Handler does not need to close anything
// Function defined for future compatibility
func (a *ACMEHandler) Close() error {
return nil
}
// ObtainCert obtains a certificate for the specified domains.
func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email string, caName string, caUrl string, skipTLS bool, useDNS bool, propagationTimeout int) (bool, error) {
a.Logf("Obtaining certificate for: "+strings.Join(domains, ", "), nil)

View File

@ -308,7 +308,6 @@ func (a *AutoRenewer) CheckAndRenewCertificates() ([]string, error) {
}
if CertExpireSoon(certBytes, a.EarlyRenewDays) || CertIsExpired(certBytes) {
//This cert is expired
DNSName, err := ExtractDomains(certBytes)
if err != nil {
//Maybe self signed. Ignore this
@ -355,6 +354,7 @@ func (a *AutoRenewer) CheckAndRenewCertificates() ([]string, error) {
return a.renewExpiredDomains(expiredCertList)
}
// Close the auto renewer
func (a *AutoRenewer) Close() {
if a.TickerstopChan != nil {
a.TickerstopChan <- true
@ -440,7 +440,7 @@ func (a *AutoRenewer) HanldeSetEAB(w http.ResponseWriter, r *http.Request) {
}
// Handle update auto renew DNS configuration
func (a *AutoRenewer) HanldeSetDNS(w http.ResponseWriter, r *http.Request) {
func (a *AutoRenewer) HandleSetDNS(w http.ResponseWriter, r *http.Request) {
dnsProvider, err := utils.PostPara(r, "dnsProvider")
if err != nil {
utils.SendErrorResponse(w, "dnsProvider not set")

View File

@ -3,7 +3,7 @@ package acme
/*
CA.go
This script load CA defination from embedded ca.json
This script load CA definition from embedded ca.json
*/
import (
_ "embed"
@ -13,7 +13,7 @@ import (
"strings"
)
// CA Defination, load from embeded json when startup
// CA definition, load from embeded json when startup
type CaDef struct {
Production map[string]string
Test map[string]string

View File

@ -210,8 +210,8 @@ func (a *AuthAgent) Logout(w http.ResponseWriter, r *http.Request) error {
}
session.Values["authenticated"] = false
session.Values["username"] = nil
session.Save(r, w)
return nil
session.Options.MaxAge = -1
return session.Save(r, w)
}
// Get the current session username from request
@ -339,6 +339,7 @@ func (a *AuthAgent) CheckAuth(r *http.Request) bool {
if err != nil {
return false
}
// Check if user is authenticated
if auth, ok := session.Values["authenticated"].(bool); !ok || !auth {
return false

View File

@ -1,7 +1,6 @@
package dynamicproxy
import (
"log"
"net/http"
"os"
"path/filepath"
@ -16,7 +15,7 @@ func (h *ProxyHandler) handleAccessRouting(ruleID string, w http.ResponseWriter,
accessRule, err := h.Parent.Option.AccessController.GetAccessRuleByID(ruleID)
if err != nil {
//Unable to load access rule. Target rule not found?
log.Println("[Proxy] Unable to load access rule: " + ruleID)
h.Parent.Option.Logger.PrintAndLog("proxy-access", "Unable to load access rule: "+ruleID, err)
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("500 - Internal Server Error"))
return true

View File

@ -9,8 +9,15 @@ package domainsniff
*/
import (
"crypto/tls"
"encoding/json"
"fmt"
"net"
"net/http"
"strings"
"time"
"imuslab.com/zoraxy/mod/utils"
)
// Check if the domain is reachable and return err if not reachable
@ -25,7 +32,115 @@ func DomainReachableWithError(domain string) error {
return nil
}
// Check if a domain have TLS but it is self-signed or expired
// Return false if sniff error
func DomainIsSelfSigned(domain string) bool {
//Extract the domain from URl in case the user input the full URL
host, port, err := net.SplitHostPort(domain)
if err != nil {
host = domain
} else {
domain = host + ":" + port
}
if !strings.Contains(domain, ":") {
domain = domain + ":443"
}
//Get the certificate
conn, err := net.Dial("tcp", domain)
if err != nil {
return false
}
defer conn.Close()
//Connect with TLS using secure verify
tlsConn := tls.Client(conn, nil)
err = tlsConn.Handshake()
if err == nil {
//This is a valid certificate
fmt.Println()
return false
}
//Connect with TLS using insecure skip verify
config := &tls.Config{
InsecureSkipVerify: true,
}
tlsConn = tls.Client(conn, config)
err = tlsConn.Handshake()
//If the handshake is successful, this is a self-signed certificate
return err == nil
}
// Check if domain reachable
func DomainReachable(domain string) bool {
return DomainReachableWithError(domain) == nil
}
// Check if domain is served by a web server using HTTPS
func DomainUsesTLS(targetURL string) bool {
//Check if the site support https
httpsUrl := fmt.Sprintf("https://%s", targetURL)
httpUrl := fmt.Sprintf("http://%s", targetURL)
client := http.Client{Timeout: 5 * time.Second}
resp, err := client.Head(httpsUrl)
if err == nil && resp.StatusCode == http.StatusOK {
return true
}
resp, err = client.Head(httpUrl)
if err == nil && resp.StatusCode == http.StatusOK {
return false
}
//If the site is not reachable, return false
return false
}
/*
Request Handlers
*/
//Check if site support TLS
//Pass in ?selfsignchk=true to also check for self-signed certificate
func HandleCheckSiteSupportTLS(w http.ResponseWriter, r *http.Request) {
targetURL, err := utils.PostPara(r, "url")
if err != nil {
utils.SendErrorResponse(w, "invalid url given")
return
}
//If the selfsign flag is set, also chec for self-signed certificate
_, err = utils.PostBool(r, "selfsignchk")
if err == nil {
//Return the https and selfsign status
type result struct {
Protocol string `json:"protocol"`
SelfSign bool `json:"selfsign"`
}
scanResult := result{Protocol: "http", SelfSign: false}
if DomainUsesTLS(targetURL) {
scanResult.Protocol = "https"
if DomainIsSelfSigned(targetURL) {
scanResult.SelfSign = true
}
}
js, _ := json.Marshal(scanResult)
utils.SendJSONResponse(w, string(js))
return
}
if DomainUsesTLS(targetURL) {
js, _ := json.Marshal("https")
utils.SendJSONResponse(w, string(js))
return
} else {
js, _ := json.Marshal("http")
utils.SendJSONResponse(w, string(js))
return
}
}

View File

@ -109,6 +109,8 @@ func NewDynamicProxyCore(target *url.URL, prepender string, dpcOptions *DpcoreOp
thisTransporter.(*http.Transport).MaxConnsPerHost = optimalConcurrentConnection * 2
thisTransporter.(*http.Transport).DisableCompression = true
//TODO: Add user adjustable timeout option here
if dpcOptions.IgnoreTLSVerification {
//Ignore TLS certificate validation error
thisTransporter.(*http.Transport).TLSClientConfig.InsecureSkipVerify = true

View File

@ -291,7 +291,7 @@ func (router *Router) Restart() error {
return err
}
time.Sleep(300 * time.Millisecond)
time.Sleep(800 * time.Millisecond)
// Start the server
err = router.StartProxyService()
if err != nil {

View File

@ -3,6 +3,7 @@ package geodb
import (
_ "embed"
"net/http"
"time"
"imuslab.com/zoraxy/mod/database"
"imuslab.com/zoraxy/mod/netutils"
@ -15,17 +16,22 @@ var geoipv4 []byte //Geodb dataset for ipv4
var geoipv6 []byte //Geodb dataset for ipv6
type Store struct {
geodb [][]string //Parsed geodb list
geodbIpv6 [][]string //Parsed geodb list for ipv6
geotrie *trie
geotrieIpv6 *trie
sysdb *database.Database
option *StoreOptions
geodb [][]string //Parsed geodb list
geodbIpv6 [][]string //Parsed geodb list for ipv6
geotrie *trie
geotrieIpv6 *trie
sysdb *database.Database
slowLookupCacheIpv4 map[string]string //Cache for slow lookup
slowLookupCacheIpv6 map[string]string //Cache for slow lookup
cacheClearTicker *time.Ticker //Ticker for clearing cache
cacheClearTickerStopChan chan bool //Stop channel for cache clear ticker
option *StoreOptions
}
type StoreOptions struct {
AllowSlowIpv4LookUp bool
AllowSloeIpv6Lookup bool
AllowSlowIpv4LookUp bool
AllowSlowIpv6Lookup bool
SlowLookupCacheClearInterval time.Duration //Clear slow lookup cache interval
}
type CountryInfo struct {
@ -50,18 +56,44 @@ func NewGeoDb(sysdb *database.Database, option *StoreOptions) (*Store, error) {
}
var ipv6Trie *trie
if !option.AllowSloeIpv6Lookup {
if !option.AllowSlowIpv6Lookup {
ipv6Trie = constrctTrieTree(parsedGeoDataIpv6)
}
return &Store{
geodb: parsedGeoData,
geotrie: ipv4Trie,
geodbIpv6: parsedGeoDataIpv6,
geotrieIpv6: ipv6Trie,
sysdb: sysdb,
option: option,
}, nil
if option.SlowLookupCacheClearInterval == 0 {
option.SlowLookupCacheClearInterval = 15 * time.Minute
}
//Create a new store
thisGeoDBStore := &Store{
geodb: parsedGeoData,
geotrie: ipv4Trie,
geodbIpv6: parsedGeoDataIpv6,
geotrieIpv6: ipv6Trie,
sysdb: sysdb,
slowLookupCacheIpv4: make(map[string]string),
slowLookupCacheIpv6: make(map[string]string),
cacheClearTicker: time.NewTicker(option.SlowLookupCacheClearInterval),
cacheClearTickerStopChan: make(chan bool),
option: option,
}
//Start cache clear ticker
if option.AllowSlowIpv4LookUp || option.AllowSlowIpv6Lookup {
go func(store *Store) {
for {
select {
case <-store.cacheClearTickerStopChan:
return
case <-thisGeoDBStore.cacheClearTicker.C:
thisGeoDBStore.slowLookupCacheIpv4 = make(map[string]string)
thisGeoDBStore.slowLookupCacheIpv6 = make(map[string]string)
}
}
}(thisGeoDBStore)
}
return thisGeoDBStore, nil
}
func (s *Store) ResolveCountryCodeFromIP(ipstring string) (*CountryInfo, error) {
@ -73,8 +105,12 @@ func (s *Store) ResolveCountryCodeFromIP(ipstring string) (*CountryInfo, error)
}
// Close the store
func (s *Store) Close() {
if s.option.AllowSlowIpv4LookUp || s.option.AllowSlowIpv6Lookup {
//Stop cache clear ticker
s.cacheClearTickerStopChan <- true
}
}
func (s *Store) GetRequesterCountryISOCode(r *http.Request) string {

View File

@ -44,6 +44,7 @@ func TestResolveCountryCodeFromIP(t *testing.T) {
store, err := geodb.NewGeoDb(nil, &geodb.StoreOptions{
false,
true,
0,
})
if err != nil {
t.Errorf("error creating store: %v", err)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -56,6 +56,12 @@ func (s *Store) slowSearchIpv4(ipAddr string) string {
if isReservedIP(ipAddr) {
return ""
}
//Check if already in cache
if cc, ok := s.slowLookupCacheIpv4[ipAddr]; ok {
return cc
}
for _, ipRange := range s.geodb {
startIp := ipRange[0]
endIp := ipRange[1]
@ -63,6 +69,8 @@ func (s *Store) slowSearchIpv4(ipAddr string) string {
inRange, _ := isIPv4InRange(startIp, endIp, ipAddr)
if inRange {
//Add to cache
s.slowLookupCacheIpv4[ipAddr] = cc
return cc
}
}
@ -73,6 +81,12 @@ func (s *Store) slowSearchIpv6(ipAddr string) string {
if isReservedIP(ipAddr) {
return ""
}
//Check if already in cache
if cc, ok := s.slowLookupCacheIpv6[ipAddr]; ok {
return cc
}
for _, ipRange := range s.geodbIpv6 {
startIp := ipRange[0]
endIp := ipRange[1]
@ -80,6 +94,8 @@ func (s *Store) slowSearchIpv6(ipAddr string) string {
inRange, _ := isIPv6InRange(startIp, endIp, ipAddr)
if inRange {
//Add to cache
s.slowLookupCacheIpv6[ipAddr] = cc
return cc
}
}

View File

@ -169,9 +169,16 @@ func (n *NetStatBuffers) HandleGetBufferedNetworkInterfaceStats(w http.ResponseW
}
func (n *NetStatBuffers) Close() {
n.StopChan <- true
time.Sleep(300 * time.Millisecond)
n.EventTicker.Stop()
//Fixed issue #394 for stopping netstat listener on platforms not supported platforms
if n.StopChan != nil {
n.StopChan <- true
time.Sleep(300 * time.Millisecond)
}
if n.EventTicker != nil {
n.EventTicker.Stop()
}
}
func (n *NetStatBuffers) HandleGetNetworkInterfaceStats(w http.ResponseWriter, r *http.Request) {
@ -270,11 +277,11 @@ func (n *NetStatBuffers) GetNetworkInterfaceStats() (int64, int64, error) {
allIfaceRxByteFiles, err := filepath.Glob("/sys/class/net/*/statistics/rx_bytes")
if err != nil {
//Permission denied
return 0, 0, errors.New("Access denied")
return 0, 0, errors.New("access denied")
}
if len(allIfaceRxByteFiles) == 0 {
return 0, 0, errors.New("No valid iface found")
return 0, 0, errors.New("no valid iface found")
}
rxSum := int64(0)
@ -334,5 +341,5 @@ func (n *NetStatBuffers) GetNetworkInterfaceStats() (int64, int64, error) {
return 0, 0, nil //no ethernet adapters with en*/<Link#*>
}
return 0, 0, errors.New("Platform not supported")
return 0, 0, errors.New("platform not supported")
}

View File

@ -1,7 +1,7 @@
package v308
/*
v307 type definations
v307 type definitions
This file wrap up the self-contained data structure
for v3.0.7 structure and allow automatic updates

View File

@ -1,7 +1,7 @@
package v308
/*
v308 type definations
v308 type definition
This file wrap up the self-contained data structure
for v3.0.8 structure and allow automatic updates

View File

@ -27,18 +27,18 @@ func ReverseProxtInit() {
/*
Load Reverse Proxy Global Settings
*/
inboundPort := 80
inboundPort := 443
if sysdb.KeyExists("settings", "inbound") {
sysdb.Read("settings", "inbound", &inboundPort)
SystemWideLogger.Println("Serving inbound port ", inboundPort)
} else {
SystemWideLogger.Println("Inbound port not set. Using default (80)")
SystemWideLogger.Println("Inbound port not set. Using default (443)")
}
useTls := false
useTls := true
sysdb.Read("settings", "usetls", &useTls)
if useTls {
SystemWideLogger.Println("TLS mode enabled. Serving proxxy request with TLS")
SystemWideLogger.Println("TLS mode enabled. Serving proxy request with TLS")
} else {
SystemWideLogger.Println("TLS mode disabled. Serving proxy request with plain http")
}
@ -59,7 +59,7 @@ func ReverseProxtInit() {
SystemWideLogger.Println("Development mode disabled. Proxying with default Cache Control policy")
}
listenOnPort80 := false
listenOnPort80 := true
sysdb.Read("settings", "listenP80", &listenOnPort80)
if listenOnPort80 {
SystemWideLogger.Println("Port 80 listener enabled")
@ -67,7 +67,7 @@ func ReverseProxtInit() {
SystemWideLogger.Println("Port 80 listener disabled")
}
forceHttpsRedirect := false
forceHttpsRedirect := true
sysdb.Read("settings", "redirect", &forceHttpsRedirect)
if forceHttpsRedirect {
SystemWideLogger.Println("Force HTTPS mode enabled")
@ -85,7 +85,7 @@ func ReverseProxtInit() {
dprouter, err := dynamicproxy.NewDynamicProxy(dynamicproxy.RouterOption{
HostUUID: nodeUUID,
HostVersion: version,
HostVersion: SYSTEM_VERSION,
Port: inboundPort,
UseTls: useTls,
ForceTLSLatest: forceLatestTLSVersion,
@ -1085,6 +1085,7 @@ func HandleIncomingPortSet(w http.ResponseWriter, r *http.Request) {
if dynamicProxyRouter.Running {
dynamicProxyRouter.StopProxyService()
dynamicProxyRouter.Option.Port = newIncomingPortInt
time.Sleep(1 * time.Second) //Fixed start fail issue
dynamicProxyRouter.StartProxyService()
} else {
//Only change setting but not starting the proxy service
@ -1173,7 +1174,7 @@ func HandleCustomHeaderAdd(w http.ResponseWriter, r *http.Request) {
return
}
//Create a Custom Header Defination type
//Create a Custom Header Definition type
var rewriteDirection rewrite.HeaderDirection
if direction == "toOrigin" {
rewriteDirection = rewrite.HeaderDirection_ZoraxyToUpstream

View File

@ -27,7 +27,7 @@ func FSHandler(handler http.Handler) http.Handler {
Development Mode Override
=> Web root is located in /
*/
if development && strings.HasPrefix(r.URL.Path, "/web/") {
if DEVELOPMENT_BUILD && strings.HasPrefix(r.URL.Path, "/web/") {
u, _ := url.Parse(strings.TrimPrefix(r.URL.Path, "/web"))
r.URL = u
}
@ -36,7 +36,7 @@ func FSHandler(handler http.Handler) http.Handler {
Production Mode Override
=> Web root is located in /web
*/
if !development && r.URL.Path == "/" {
if !DEVELOPMENT_BUILD && r.URL.Path == "/" {
//Redirect to web UI
http.Redirect(w, r, "/web/", http.StatusTemporaryRedirect)
return
@ -93,7 +93,7 @@ func FSHandler(handler http.Handler) http.Handler {
// Production path fix wrapper. Fix the path on production or development environment
func ppf(relativeFilepath string) string {
if !development {
if !DEVELOPMENT_BUILD {
return strings.ReplaceAll(filepath.Join("/web/", relativeFilepath), "\\", "/")
}
return relativeFilepath
@ -111,7 +111,7 @@ func handleInjectHTML(w http.ResponseWriter, r *http.Request, relativeFilepath s
if len(relativeFilepath) > 0 && relativeFilepath[len(relativeFilepath)-1:] == "/" {
relativeFilepath = relativeFilepath + "index.html"
}
if development {
if DEVELOPMENT_BUILD {
//Load from disk
targetFilePath := strings.ReplaceAll(filepath.Join("web/", relativeFilepath), "\\", "/")
content, err = os.ReadFile(targetFilePath)

View File

@ -52,19 +52,19 @@ var (
func startupSequence() {
//Start a system wide logger and log viewer
l, err := logger.NewLogger("zr", "./log")
l, err := logger.NewLogger(LOG_PREFIX, LOG_FOLDER)
if err == nil {
SystemWideLogger = l
} else {
panic(err)
}
LogViewer = logviewer.NewLogViewer(&logviewer.ViewerOption{
RootFolder: "./log",
Extension: ".log",
RootFolder: LOG_FOLDER,
Extension: LOG_EXTENSION,
})
//Create database
db, err := database.NewDatabase("sys.db", false)
db, err := database.NewDatabase(DATABASE_PATH, false)
if err != nil {
log.Fatal(err)
}
@ -73,21 +73,21 @@ func startupSequence() {
sysdb.NewTable("settings")
//Create tmp folder and conf folder
os.MkdirAll("./tmp", 0775)
os.MkdirAll("./conf/proxy/", 0775)
os.MkdirAll(TMP_FOLDER, 0775)
os.MkdirAll(CONF_HTTP_PROXY, 0775)
//Create an auth agent
sessionKey, err := auth.GetSessionKey(sysdb, SystemWideLogger)
if err != nil {
log.Fatal(err)
}
authAgent = auth.NewAuthenticationAgent(name, []byte(sessionKey), sysdb, true, SystemWideLogger, func(w http.ResponseWriter, r *http.Request) {
authAgent = auth.NewAuthenticationAgent(SYSTEM_NAME, []byte(sessionKey), sysdb, true, SystemWideLogger, func(w http.ResponseWriter, r *http.Request) {
//Not logged in. Redirecting to login page
http.Redirect(w, r, ppf("/login.html"), http.StatusTemporaryRedirect)
})
//Create a TLS certificate manager
tlsCertManager, err = tlscert.NewManager("./conf/certs", development, SystemWideLogger)
tlsCertManager, err = tlscert.NewManager(CONF_CERT_STORE, DEVELOPMENT_BUILD, SystemWideLogger)
if err != nil {
panic(err)
}
@ -96,15 +96,16 @@ func startupSequence() {
db.NewTable("redirect")
redirectAllowRegexp := false
db.Read("redirect", "regex", &redirectAllowRegexp)
redirectTable, err = redirection.NewRuleTable("./conf/redirect", redirectAllowRegexp, SystemWideLogger)
redirectTable, err = redirection.NewRuleTable(CONF_REDIRECTION, redirectAllowRegexp, SystemWideLogger)
if err != nil {
panic(err)
}
//Create a geodb store
geodbStore, err = geodb.NewGeoDb(sysdb, &geodb.StoreOptions{
AllowSlowIpv4LookUp: !*enableHighSpeedGeoIPLookup,
AllowSloeIpv6Lookup: !*enableHighSpeedGeoIPLookup,
AllowSlowIpv4LookUp: !*enableHighSpeedGeoIPLookup,
AllowSlowIpv6Lookup: !*enableHighSpeedGeoIPLookup,
SlowLookupCacheClearInterval: GEODB_CACHE_CLEAR_INTERVAL * time.Minute,
})
if err != nil {
panic(err)
@ -121,7 +122,7 @@ func startupSequence() {
accessController, err = access.NewAccessController(&access.Options{
Database: sysdb,
GeoDB: geodbStore,
ConfigFolder: "./conf/access",
ConfigFolder: CONF_ACCESS_RULE,
})
if err != nil {
panic(err)
@ -154,7 +155,7 @@ func startupSequence() {
//Start the static web server
staticWebServer = webserv.NewWebServer(&webserv.WebServerOptions{
Sysdb: sysdb,
Port: "5487", //Default Port
Port: strconv.Itoa(WEBSERV_DEFAULT_PORT), //Default Port
WebRoot: *staticWebServerRoot,
EnableDirectoryListing: true,
EnableWebDirManager: *allowWebFileManager,
@ -179,7 +180,7 @@ func startupSequence() {
pathRuleHandler = pathrule.NewPathRuleHandler(&pathrule.Options{
Enabled: false,
ConfigFolder: "./conf/rules/pathrules",
ConfigFolder: CONF_PATH_RULE,
})
/*
@ -197,7 +198,7 @@ func startupSequence() {
hostName := *mdnsName
if hostName == "" {
hostName = "zoraxy_" + nodeUUID
hostName = MDNS_HOSTNAME_PREFIX + nodeUUID
} else {
//Trim off the suffix
hostName = strings.TrimSuffix(hostName, ".local")
@ -206,24 +207,24 @@ func startupSequence() {
mdnsScanner, err = mdns.NewMDNS(mdns.NetworkHost{
HostName: hostName,
Port: portInt,
Domain: "zoraxy.aroz.org",
Model: "Network Gateway",
Domain: MDNS_IDENTIFY_DOMAIN,
Model: MDNS_IDENTIFY_DEVICE_TYPE,
UUID: nodeUUID,
Vendor: "imuslab.com",
BuildVersion: version,
Vendor: MDNS_IDENTIFY_VENDOR,
BuildVersion: SYSTEM_VERSION,
}, "")
if err != nil {
SystemWideLogger.Println("Unable to startup mDNS service. Disabling mDNS services")
} else {
//Start initial scanning
go func() {
hosts := mdnsScanner.Scan(30, "")
hosts := mdnsScanner.Scan(MDNS_SCAN_TIMEOUT, "")
previousmdnsScanResults = hosts
SystemWideLogger.Println("mDNS Startup scan completed")
}()
//Create a ticker to update mDNS results every 5 minutes
ticker := time.NewTicker(15 * time.Minute)
ticker := time.NewTicker(MDNS_SCAN_UPDATE_INTERVAL * time.Minute)
stopChan := make(chan bool)
go func() {
for {
@ -231,7 +232,7 @@ func startupSequence() {
case <-stopChan:
ticker.Stop()
case <-ticker.C:
hosts := mdnsScanner.Scan(30, "")
hosts := mdnsScanner.Scan(MDNS_SCAN_TIMEOUT, "")
previousmdnsScanResults = hosts
SystemWideLogger.Println("mDNS scan result updated")
}
@ -265,7 +266,7 @@ func startupSequence() {
//Create TCP Proxy Manager
streamProxyManager, err = streamproxy.NewStreamProxy(&streamproxy.Options{
AccessControlHandler: accessController.DefaultAccessRule.AllowConnectionAccess,
ConfigStore: "./conf/streamproxy",
ConfigStore: CONF_STREAM_PROXY,
Logger: SystemWideLogger,
})
if err != nil {
@ -303,8 +304,8 @@ func startupSequence() {
sysdb.NewTable("acmepref")
acmeHandler = initACME()
acmeAutoRenewer, err = acme.NewAutoRenewer(
"./conf/acme_conf.json",
"./conf/certs/",
ACME_AUTORENEW_CONFIG_PATH,
CONF_CERT_STORE,
int64(*acmeAutoRenewInterval),
*acmeCertAutoRenewDays,
acmeHandler,

View File

@ -841,6 +841,25 @@
function initBannedCountryList(){
$.get("/api/blacklist/list?type=country&id=" + currentEditingAccessRule, function(data) {
let bannedListHtml = '';
//Check if the country code list contains all eu countries. If yes, replace it with "EU"
let allEu = true;
let euCountries = getEUCCs();
for (var i = 0; i < euCountries.length; i++){
if (!data.includes(euCountries[i])){
allEu = false;
break;
}
}
if (allEu){
//Remove EU countries from the list and replace it with EU
data = data.filter(function(value, index, arr){
return !euCountries.includes(value);
});
data.push("eu");
}
data.forEach((countryCode) => {
bannedListHtml += `
<tr>
@ -919,18 +938,48 @@
//Whitelist country table
function initWhitelistCountryList(){
$.get("/api/whitelist/list?type=country&id=" + currentEditingAccessRule, function(data) {
let bannedListHtml = '';
let whiteListHTML = '';
//Check if the country code list contains all eu countries. If yes, replace it with "EU"
let allEu = true;
let euCountries = getEUCCs();
let countryCodesIndata = data.map(function(item){
//data[n].CC is the country code
return item.CC;
});
for (var i = 0; i < euCountries.length; i++){
if (!countryCodesIndata.includes(euCountries[i])){
allEu = false;
break;
}
}
if (allEu){
//Remove EU countries from the list and replace it with EU
data = data.filter(function(value, index, arr){
return !euCountries.includes(value.CC);
});
data.push({
CC: "eu"
});
}
data.forEach((countryWhitelistEntry) => {
let countryCode = countryWhitelistEntry.CC;
bannedListHtml += `
whiteListHTML += `
<tr>
<td><i class="${countryCode} flag"></i> ${getCountryName(countryCode)} (${countryCode.toUpperCase()})</td>
<td><button class="ui red basic mini icon button" onclick="removeFromWhiteList('${countryCode}')"><i class="trash icon"></i></button></td>
</tr>
`;
});
$('#whitelistCountryList').html(bannedListHtml);
filterCountries(data, "#countrySelectorWhitelist .menu .item");
$('#whitelistCountryList').html(whiteListHTML);
//Map the data.CC to the country code
let countryCodes = data.map(function(item){
return item.CC;
});
filterCountries(countryCodes, "#countrySelectorWhitelist .menu .item");
if (data.length === 0) {
$('#whitelistCountryList').append(`
<tr>
@ -1016,6 +1065,10 @@
});
}
function getEUCCs(){
return ["at","be","bg","cy","cz","de","dk","ee","es","fi","fr","gr","hr","hu","ie","it","lt","lu","lv","mt","nl","pl","pt","se","si","sk"];
}
function addCountryToBlacklist() {
var countryCode = $("#countrySelector").dropdown("get value").toLowerCase();
let ccs = [countryCode];
@ -1025,48 +1078,50 @@
ccs = countryCode.split(",");
}
let counter = 0;
for(var i = 0; i < ccs.length; i++){
let thisCountryCode = ccs[i];
$.cjax({
type: "POST",
url: "/api/blacklist/country/add",
method: "POST",
data: { cc: thisCountryCode, id: currentEditingAccessRule},
success: function(response) {
if (response.error != undefined){
msgbox(response.error, false);
}
if (counter == (ccs.length - 1)){
//Last item
setTimeout(function(){
initBannedCountryList();
if (ccs.length == 1){
//Single country
msgbox(`Added ${getCountryName(ccs[0])} to blacklist`);
}else{
msgbox(ccs.length + " countries added to blacklist");
}
}, (ccs.length==1)?0:100);
}
counter++;
},
error: function(xhr, status, error) {
// handle error response
}
//If the ccs includes "eu", remove the "eu" and add all eu country code to the list
if (ccs.includes("eu")){
ccs = ccs.concat(getEUCCs());
ccs = ccs.filter(function(item){
return item != "eu";
});
}
let counter = ccs.length;
$.cjax({
type: "POST",
url: "/api/blacklist/country/add",
method: "POST",
data: { cc: ccs.join(","), id: currentEditingAccessRule},
success: function(response) {
if (response.error != undefined){
msgbox(response.error, false);
}
initBannedCountryList();
if (ccs.length == 1){
//Single country
msgbox(`Added ${getCountryName(ccs[0])} to blacklist`);
}else{
msgbox(ccs.length + " countries added to blacklist");
}
},
error: function(xhr, status, error) {
// handle error response
}
});
$('#countrySelector').dropdown('clear');
}
function removeFromBannedList(countryCode){
countryCode = countryCode.toLowerCase();
let countryName = getCountryName(countryCode);
if (countryCode == "eu"){
let euCountries = getEUCCs();
countryCode = euCountries.join(",");
countryName = "European Union";
}else{
countryCode = countryCode.toLowerCase();
}
$.cjax({
url: "/api/blacklist/country/remove",
method: "POST",
@ -1162,44 +1217,53 @@
//Usually just a few countries a for loop will get the job done
ccs = countryCode.split(",");
}
let counter = 0;
for(var i = 0; i < ccs.length; i++){
let thisCountryCode = ccs[i];
$.cjax({
type: "POST",
url: "/api/whitelist/country/add",
data: { cc: thisCountryCode , id: currentEditingAccessRule},
success: function(response) {
if (response.error != undefined){
msgbox(response.error, false);
}
if (counter == (ccs.length - 1)){
setTimeout(function(){
initWhitelistCountryList();
if (ccs.length == 1){
//Single country
msgbox(`Added ${getCountryName(ccs[0])} to whitelist`);
}else{
msgbox(ccs.length + " countries added to whitelist");
}
}, (ccs.length==1)?0:100);
}
counter++;
},
error: function(xhr, status, error) {
// handle error response
}
//If the ccs includes "eu", remove the "eu" and add all eu country code to the list
if (ccs.includes("eu")){
ccs = ccs.filter(function(item){
return item != "eu";
});
ccs = ccs.concat(getEUCCs());
}
let counter = ccs.length;
$.cjax({
type: "POST",
url: "/api/whitelist/country/add",
data: { cc: ccs.join(",") , id: currentEditingAccessRule},
success: function(response) {
if (response.error != undefined){
msgbox(response.error, false);
}
initWhitelistCountryList();
if (ccs.length == 1){
//Single country
msgbox(`Added ${getCountryName(ccs[0])} to whitelist`);
}else{
msgbox(ccs.length + " countries added to whitelist");
}
},
error: function(xhr, status, error) {
// handle error response
}
});
$('#countrySelectorWhitelist').dropdown('clear');
}
function removeFromWhiteList(countryCode){
if (confirm("Confirm removing " + getCountryName(countryCode) + " from whitelist?")){
//Remove from whitelist, accepts a country code or "eu" for all EU countries
function removeFromWhiteList(countryCode, skipConfirm = true){
let countryName = getCountryName(countryCode);
if (countryCode == "eu"){
let euCountries = getEUCCs();
countryCode = euCountries.join(",");
countryName = "European Union";
}else{
countryCode = countryCode.toLowerCase();
}
if (skipConfirm || confirm("Confirm removing " + getCountryName(countryCode) + " from whitelist?")){
$.cjax({
url: "/api/whitelist/country/remove",
method: "POST",
@ -1208,6 +1272,7 @@
if (response.error != undefined){
msgbox(response.error, false);
}
msgbox(countryName + " removed from whitelist");
initWhitelistCountryList();
},
error: function(xhr, status, error) {
@ -1276,19 +1341,27 @@
/*
Common Utilities
*/
function filterCountries(codesToShow, selector="#countrySelector .menu .item") {
function filterCountries(alreadySelectedCCs, selector="#countrySelector .menu .item") {
// get all items in the dropdown
const items = document.querySelectorAll(selector);
const euCountries = getEUCCs();
//Replce "eu" in alreadySelectedCCs with all EU countries
if (alreadySelectedCCs.includes("eu")){
alreadySelectedCCs = alreadySelectedCCs.filter(function(item){
return item != "eu";
});
alreadySelectedCCs = alreadySelectedCCs.concat(euCountries);
}
// loop through all items
items.forEach(item => {
// get the value of the item (i.e. the country code)
const code = item.dataset.value;
// if the code is in the array of codes to show, show the item
if (codesToShow.includes(code)) {
if (alreadySelectedCCs.includes(code)) {
//This country code already selected. Hide it
item.style.display = 'none';
}
// otherwise, hide the item
else {
} else {
// otherwise, show the item
item.style.display = 'block';
}
});

View File

@ -101,7 +101,7 @@
<p style="margin-bottom: 0.4em;"><i class="ui upload icon"></i> Upload Default Keypairs</p>
<div class="ui buttons">
<button class="ui basic grey button" onclick="uploadPublicKey();"><i class="globe icon"></i> Public Key</button>
<button class="ui basic black button" onclick="uploadPrivateKey();"><i class="black lock icon"></i> Private Key</button>
<button class="ui basic button" onclick="uploadPrivateKey();"><i class="grey lock icon"></i> Private Key</button>
</div>
</div>
<div class="ui divider"></div>

View File

@ -30,7 +30,7 @@
<i class="ui green checkmark icon"></i> Redirection Rule Deleted
</div>
<!-- Options -->
<div class="ui basic segment" style="background-color: #f7f7f7; border-radius: 1em;">
<div class="ui basic segment advanceoptions">
<div class="ui accordion advanceSettings">
<div class="title">
<i class="dropdown icon"></i>
@ -173,7 +173,7 @@
});
if (data.length == 0){
$("#redirectionRuleList").append(`<tr colspan="4"><td><i class="green check circle icon"></i> No redirection rule</td></tr>`);
$("#redirectionRuleList").append(`<tr><td colspan="5"><i class="green check circle icon"></i> No redirection rule</td></tr>`);
}
});

View File

@ -50,7 +50,7 @@
</div>
</div>
<!-- Advance configs -->
<div class="ui basic segment" style="background-color: #f7f7f7; border-radius: 1em;">
<div class="ui basic segment advanceoptions">
<div id="advanceProxyRules" class="ui fluid accordion">
<div class="title">
<i class="dropdown icon"></i>
@ -295,15 +295,25 @@
//Automatic check if the site require TLS and check the checkbox if needed
function autoCheckTls(targetDomain){
$.cjax({
url: "/api/proxy/tlscheck",
url: "/api/proxy/tlscheck?selfsignchk=true",
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"){
$("#reqTls").parent().checkbox("set unchecked");
}else{
//Check if the site require TLS
if (data.protocol == "https"){
$("#reqTls").parent().checkbox("set checked");
}else if (data.protocol == "http"){
$("#reqTls").parent().checkbox("set unchecked");
}
//Check if the site is using self-signed cert
if (data.selfsign){
$("#skipTLSValidation").parent().checkbox("set checked");
}else{
$("#skipTLSValidation").parent().checkbox("set unchecked");
}
}
}
})

View File

@ -73,28 +73,30 @@
<p>Inbound Port (Reverse Proxy Listening Port)</p>
<div class="ui action fluid notloopbackOnly input" tourstep="incomingPort">
<small id="applyButtonReminder">Click "Apply" button to confirm listening port changes</small>
<input type="text" id="incomingPort" placeholder="Incoming Port" value="80">
<input type="text" id="incomingPort" placeholder="Incoming Port" value="443">
<button class="ui green notloopbackOnly button" style="background: linear-gradient(60deg, #27e7ff, #00ca52);" onclick="handlePortChange();"><i class="ui checkmark icon"></i> Apply</button>
</div>
<br>
<div id="tls" class="ui toggle notloopbackOnly checkbox">
<input type="checkbox">
<label>Use TLS to serve proxy request</label>
<label>Use TLS to serve proxy request<br>
<small>Also known as HTTPS mode</small></label>
</div>
<br>
<div id="listenP80" class="ui toggle notloopbackOnly tlsEnabledOnly checkbox" style="margin-top: 0.6em;" >
<div id="listenP80" class="ui toggle notloopbackOnly tlsEnabledOnly checkbox" style="margin-top: 0.4em;" >
<input type="checkbox">
<label>Enable HTTP server on port 80<br>
<small>(Only apply when TLS enabled and not using port 80)</small></label>
<small>Accept HTTP requests even if you are using HTTPS mode</small></label>
</div>
<br>
<div tourstep="forceHttpsRedirect" style="display: inline-block;">
<div id="redirect" class="ui toggle notloopbackOnly tlsEnabledOnly checkbox" style="margin-top: 0.6em; padding-left: 2em;">
<div id="redirect" class="ui toggle notloopbackOnly tlsEnabledOnly checkbox" style="margin-top: 0.4em;">
<input type="checkbox">
<label>Force redirect HTTP request to HTTPS</label>
<label>Force redirect HTTP request to HTTPS<br>
<small>Redirect web traffic from port 80 to 443, require enabling HTTP server on port 80</small></label>
</div>
</div>
<div class="ui basic segment" style="background-color: #f7f7f7; border-radius: 1em;">
<div class="ui basic segment advanceoptions">
<div class="ui accordion advanceSettings">
<div class="title">
<i class="dropdown icon"></i>
@ -359,10 +361,10 @@
return;
}
if (enabled){
$("#redirect").show();
//$("#redirect").show();
msgbox("Port 80 listener enabled");
}else{
$("#redirect").hide();
//$("#redirect").hide();
msgbox("Port 80 listener disabled");
}
}
@ -400,10 +402,10 @@
$.get("/api/proxy/listenPort80", function(data){
if (data){
$("#listenP80").checkbox("set checked");
$("#redirect").show();
//$("#redirect").show();
}else{
$("#listenP80").checkbox("set unchecked");
$("#redirect").hide();
//$("#redirect").hide();
}
$("#listenP80").find("input").on("change", function(){
@ -579,7 +581,7 @@
let timestamps = [];
for(var i = 0; i < dataCount; i++){
timestamps.push(parseInt(Date.now() / 1000) + i);
timestamps.push(new Date(Date.now() + i * 1000).toLocaleString().replace(',', ''));
}
function fetchData() {
@ -600,10 +602,8 @@
txValues.shift();
}
timestamps.push(parseInt(Date.now() / 1000));
timestamps.push(new Date(Date.now()).toLocaleString().replace(',', ''));
timestamps.shift();
updateChart();
}
})

View File

@ -70,7 +70,7 @@
initUptimeTable();
function reloadUptimeList(){
$("#utmrender").html(`<div class="ui segment">
$("#utmrender").html(`<div class="ui utmloading segment">
<div class="ui active inverted dimmer" style="z-index: 2;">
<div class="ui text loader">Loading</div>
</div>

View File

@ -69,7 +69,7 @@
</div>
<!-- Advance configs -->
<div class="ui basic segment" style="background-color: #f7f7f7; border-radius: 1em;">
<div class="ui basic segment advanceoptions">
<div id="advanceProxyRules" class="ui fluid accordion">
<div class="title">
<i class="dropdown icon"></i>
@ -191,14 +191,19 @@
var targetDomain = $("#virtualDirectoryDomain").val().trim();
if (targetDomain != ""){
$.cjax({
url: "/api/proxy/tlscheck",
url: "/api/proxy/tlscheck?selfsignchk=true",
data: {url: targetDomain},
success: function(data){
if (data.error != undefined){
}else if (data == "https"){
}else if (data.protocol == "https"){
$("#vdReqTls").parent().checkbox("set checked");
}else if (data == "http"){
if (data.selfsign){
$("#vdSkipTLSValidation").parent().checkbox("set checked");
}else{
$("#vdSkipTLSValidation").parent().checkbox("set unchecked");
}
}else if (data.protocol == "http"){
$("#vdReqTls").parent().checkbox("set unchecked");
}
}

1133
src/web/darktheme.css Normal file

File diff suppressed because it is too large Load Diff

View File

@ -16,22 +16,24 @@
<script src="script/chart.js"></script>
<script src="script/utils.js"></script>
<link rel="stylesheet" href="main.css">
<link rel="stylesheet" href="darktheme.css">
</head>
<body>
<script src="script/darktheme.js"></script>
<div class="menubar">
<div class="item">
<img class="logo" src="img/logo.svg">
</div>
<div class="ui right floated buttons menutoggle" style="padding-top: 2px;">
<button class="ui basic icon button" onclick="$('.toolbar').fadeToggle('fast');"><i class="content icon"></i></button>
<button id="sidemenuBtn" class="ui basic icon button" onclick="$('.toolbar').fadeToggle('fast');"><i class="content icon"></i></button>
</div>
<div class="ui right floated buttons" style="padding-top: 2px; padding-right: 0.4em;">
<button class="ui basic white icon button" onclick="logout();"><i class="sign-out icon"></i></button>
</div>
<!-- <div class="ui right floated buttons" style="padding-top: 2px;">
<div class="ui right floated buttons" style="padding-top: 2px; margin-right: 0.4em;">
<button id="themeColorButton" class="ui icon button" onclick="toggleTheme();"><i class="sun icon"></i></button>
</div> -->
</div>
</div>
<div class="wrapper">
<div class="toolbar">
@ -269,11 +271,18 @@
function toggleTheme(){
if ($("body").hasClass("darkTheme")){
$("body").removeClass("darkTheme")
$("#themeColorButton").html(`<i class="ui moon icon"></i>`);
setDarkTheme(false);
//Check if the snippet iframe is opened. If yes, set the dark theme to the iframe
if ($(".sideWrapper").is(":visible")){
$(".sideWrapper iframe")[0].contentWindow.setDarkTheme(false);
}
}else{
$("body").addClass("darkTheme");
$("#themeColorButton").html(`<i class="ui sun icon"></i>`);
setDarkTheme(true);
//Check if the snippet iframe is opened. If yes, set the dark theme to the iframe
if ($(".sideWrapper").is(":visible")){
$(".sideWrapper iframe")[0].contentWindow.setDarkTheme(true);
}
}
}

View File

@ -1,50 +1,8 @@
/*
index.html style overwrite
*/
:root{
--theme_background: linear-gradient(60deg, rgb(84, 58, 183) 0%, rgb(0, 172, 193) 100%);
--theme_background_inverted: linear-gradient(215deg, rgba(38,60,71,1) 13%, rgba(2,3,42,1) 84%);
--theme_green: linear-gradient(270deg, #27e7ff, #00ca52);
--theme_red: linear-gradient(203deg, rgba(250,172,38,1) 17%, rgba(202,0,37,1) 78%);
}
/* Theme Color Definations */
body:not(.darkTheme){
--theme_bg: #f6f6f6;
--theme_bg_primary: #ffffff;
--theme_bg_secondary: #ffffff;
--theme_bg_active: #ececec;
--theme_highlight: #a9d1f3;
--theme_bg_inverted: #27292d;
--theme_advance: #f8f8f9;
--item_color: #5e5d5d;
--item_color_select: rgba(0,0,0,.87);
--text_color: #414141;
--input_color: white;
--divider_color: #cacaca;
--text_color_inverted: #fcfcfc;
--button_text_color: #878787;
--button_border_color: #dedede;
}
body.darkTheme{
--theme_bg: #27292d;
--theme_bg_primary: #3d3f47;
--theme_bg_secondary: #373a42;
--theme_highlight: #6682c4;
--theme_bg_active: #292929;
--theme_bg_inverted: #f8f8f9;
--theme_advance: #333333;
--item_color: #cacaca;
--text_color: #fcfcfc;
--text_color_secondary: #dfdfdf;
--input_color: black;
--divider_color: #3b3b3b;
--item_color_select: rgba(255, 255, 255, 0.87);
--text_color_inverted: #414141;
--button_text_color: #e9e9e9;
--button_border_color: #646464;
}
/* Theme color palletes are defined in darktheme.css */
/* Theme Toggle CSS */
#themeColorButton{
@ -368,7 +326,7 @@ body{
}
.basic.segment.advanceoptions{
background-color: #f7f7f7;
background-color: var(--theme_advance);
border-radius: 1em;
}

6
src/web/robots.txt Normal file
View File

@ -0,0 +1,6 @@
# robots.txt for Zoraxy project
# In general, you should not expose the management interface to the internet.
# In case you do, this file (hopefully) protects you from web crawlers.
User-agent: *
Disallow: /

View File

@ -0,0 +1,51 @@
/*
Dark Theme Toggle Manager
This script is used to manage the dark theme toggle button in the header of the website.
It will change the theme of the website to dark mode when the toggle is clicked and back to light mode when clicked again.
Must be included just after the start of body tag in the HTML file.
*/
function _whiteThemeHandleApplyChange(){
$(".menubar .logo").attr("src", "img/logo.svg");
}
function _darkThemeHandleApplyChange(){
$(".menubar .logo").attr("src", "img/logo_white.svg");
}
//Check if the theme is dark, must be done before the body is loaded to prevent flickering
function setDarkTheme(isDarkTheme = false){
if (isDarkTheme){
$("body").addClass("darkTheme");
$("#themeColorButton").html(`<i class="ui sun icon"></i>`);
localStorage.setItem("theme", "dark");
//Check if the page is still loading, if not change the logo
if (document.readyState == "complete"){
_darkThemeHandleApplyChange();
}else{
//Wait for the page to load and then change the logo
$(document).ready(function(){
_darkThemeHandleApplyChange();
});
}
}else{
$("body").removeClass("darkTheme")
$("#themeColorButton").html(`<i class="ui moon icon"></i>`);
localStorage.setItem("theme", "light");
//By default the page is white theme. So no need to change the logo if page is still loading
if (document.readyState == "complete"){
//Switching back to light theme
_whiteThemeHandleApplyChange();
}
}
}
if (localStorage.getItem("theme") == "dark"){
setDarkTheme(true);
}else{
setDarkTheme(false);
}

View File

@ -14,69 +14,72 @@
top: 0.4em;
right: 1em;
}
</style>
</head>
<body>
<br>
<div class="ui container">
<div class="ui header">
<div class="content">
Access Rule Editor
<div class="sub header">Create, Edit or Remove Access Rules</div>
</div>
</div>
<div class="ui divider"></div>
<div class="ui top attached tabular menu">
<a class="active item" data-tab="new"><i class="ui green add icon"></i> New</a>
<a class="item" data-tab="edit"><i class="ui grey edit icon"></i> Edit</a>
</div>
<div class="ui bottom attached active tab segment" data-tab="new">
<p>Create a new Access Rule</p>
<form class="ui form" id="accessRuleForm">
<div class="field">
<label>Rule Name</label>
<input type="text" name="accessRuleName" placeholder="Rule Name" required>
</div>
<div class="field">
<label>Description</label>
<textarea name="description" placeholder="Description" required></textarea>
</div>
<button class="ui basic button" type="submit"><i class="ui green add icon"></i> Create</button>
</form>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<br>
</div>
<div class="ui bottom attached tab segment" data-tab="edit">
<p>Select an Access Rule to edit</p>
<button id="refreshAccessRuleListBtn" class="ui circular basic icon button" onclick="reloadAccessRuleList()"><i class="ui green refresh icon"></i></button>
<div class="ui selection fluid dropdown" id="accessRuleSelector">
<input type="hidden" name="targetAccessRule" value="default">
<i class="dropdown icon"></i>
<div class="default text"></div>
<div class="menu" id="accessRuleList">
<div class="item" data-value="default"><i class="ui yellow star icon"></i> Default</div>
<div class="ui container">
<div class="ui header">
<div class="content">
Access Rule Editor
<div class="sub header">Create, Edit or Remove Access Rules</div>
</div>
</div>
<div class="ui divider"></div>
<div class="ui top attached tabular menu">
<a class="active item" data-tab="new"><i class="ui green add icon"></i> New</a>
<a class="item" data-tab="edit"><i class="ui grey edit icon"></i> Edit</a>
</div>
<div class="ui bottom attached active tab segment" data-tab="new">
<p>Create a new Access Rule</p>
<form class="ui form" id="accessRuleForm">
<div class="field">
<label>Rule Name</label>
<input type="text" name="accessRuleName" placeholder="Rule Name" required>
</div>
<div class="field">
<label>Description</label>
<textarea name="description" placeholder="Description" required></textarea>
</div>
<button class="ui basic button" type="submit"><i class="ui green add icon"></i> Create</button>
</form>
<br>
</div>
<div class="ui bottom attached tab segment" data-tab="edit">
<p>Select an Access Rule to edit</p>
<button id="refreshAccessRuleListBtn" class="ui circular basic icon button" onclick="reloadAccessRuleList()"><i class="ui green refresh icon"></i></button>
<div class="ui selection fluid dropdown" id="accessRuleSelector">
<input type="hidden" name="targetAccessRule" value="default">
<i class="dropdown icon"></i>
<div class="default text"></div>
<div class="menu" id="accessRuleList">
<div class="item" data-value="default"><i class="ui yellow star icon"></i> Default</div>
</div>
</div>
<br>
<form class="ui form" id="modifyRuleInfo">
<div class="disabled field">
<label>Rule ID</label>
<input type="text" name="accessRuleUUID">
</div>
<div class="field">
<label>Rule Name</label>
<input type="text" name="accessRuleName" placeholder="Rule Name" required>
</div>
<div class="field">
<label>Description</label>
<textarea name="description" placeholder="Description" required></textarea>
</div>
<button class="ui basic button" type="submit"><i class="ui green save icon"></i> Save Changes</button>
<button class="ui basic button" onclick="removeAccessRule(event);"><i class="ui red trash icon"></i> Remove Rule</button>
</form>
</div>
<br>
<form class="ui form" id="modifyRuleInfo">
<div class="disabled field">
<label>Rule ID</label>
<input type="text" name="accessRuleUUID">
</div>
<div class="field">
<label>Rule Name</label>
<input type="text" name="accessRuleName" placeholder="Rule Name" required>
</div>
<div class="field">
<label>Description</label>
<textarea name="description" placeholder="Description" required></textarea>
</div>
<button class="ui basic button" type="submit"><i class="ui green save icon"></i> Save Changes</button>
<button class="ui basic button" onclick="removeAccessRule(event);"><i class="ui red trash icon"></i> Remove Rule</button>
</form>
</div>
<br>
<button class="ui basic button" style="float: right;" onclick="parent.hideSideWrapper();"><i class="remove icon"></i> Close</button>
<br><br><br>
<button class="ui basic button" style="float: right;" onclick="parent.hideSideWrapper();"><i class="remove icon"></i> Close</button>
<br><br><br>
</div>
<script>

View File

@ -25,6 +25,8 @@
</style>
</head>
<body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<br>
<div class="ui container">
<div class="ui header">
@ -50,7 +52,7 @@
</div>
<small>If you don't want to share your private email address, you can also fill in an email address that point to a mailbox not exists on your domain.</small>
</div>
<div class="ui basic segment" style="background-color: #f7f7f7; border-radius: 1em;">
<div class="ui basic segment advanceoptions">
<div class="ui accordion advanceSettings">
<div class="title">
<i class="dropdown icon"></i>
@ -437,11 +439,15 @@
let optionalFieldsHTML = "";
for (const [key, datatype] of Object.entries(data)) {
if (datatype == "int"){
$("#dnsProviderAPIFields").append(`<div class="ui fluid labeled dnsConfigField input" key="${key}" style="margin-top: 0.2em;">
let defaultValue = 10;
if (key == "HTTPTimeout"){
defaultValue = 300;
}
$("#dnsProviderAPIFields").append(`<div class="ui fluid labeled dnsConfigField input typeint" key="${key}" style="margin-top: 0.2em;">
<div class="ui basic blue label" style="font-weight: 300;">
${key}
</div>
<input type="number" value="300">
<input type="number" value="${defaultValue}">
</div>`);
}else if (datatype == "bool"){
booleanFieldsHTML += (`<div class="ui checkbox dnsConfigField" key="${key}" style="margin-top: 1em !important; padding-left: 0.4em;">
@ -600,8 +606,12 @@
//Boolean option
let checked = $(this).find("input")[0].checked;
dnsCredentials[thisKey] = checked;
}else if ($(this).hasClass("typeint")){
//Int options
let value = $(this).find("input").val();
dnsCredentials[thisKey] = parseInt(value);
}else{
//String or int options
//String options
let value = $(this).find("input").val().trim();
dnsCredentials[thisKey] = value;
}

View File

@ -10,6 +10,8 @@
<script src="../script/utils.js"></script>
</head>
<body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<br>
<div class="ui container">
<div class="ui header">

View File

@ -10,6 +10,8 @@
<script src="../script/utils.js"></script>
</head>
<body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<br>
<div class="ui container">
<div class="ui header">

View File

@ -10,6 +10,8 @@
<script src="../script/utils.js"></script>
</head>
<body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<br>
<div class="ui container">
<div class="ui header">

View File

@ -10,6 +10,8 @@
<script src="../script/utils.js"></script>
</head>
<body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<br>
<div class="ui container">
<div class="ui header">

View File

@ -23,9 +23,15 @@
#permissionPolicyEditor .experimental{
background-color: rgb(241, 241, 241);
}
body.darkTheme #permissionPolicyEditor .experimental{
background-color: rgb(41, 41, 41);
}
</style>
</head>
<body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<br>
<div class="ui container">
<div class="ui header">
@ -86,7 +92,7 @@
</div>
</div>
<div class="ui divider"></div>
<div class="ui basic segment" style="background-color: #f7f7f7; border-radius: 1em;">
<div class="ui basic segment advanceoptions">
<div class="ui fluid accordion">
<div class="title">
<i class="dropdown icon" tabindex="0"><div class="menu" tabindex="-1"></div></i>

View File

@ -8,6 +8,8 @@
<script src="../script/semantic/semantic.min.js"></script>
</head>
<body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<br />
<div class="ui container">
<div class="ui form">

View File

@ -41,9 +41,28 @@
min-height: 300px;
overflow-y: auto;
}
body.darkTheme #accessRuleList{
border: 1px solid rgb(50, 50, 50) !important;
}
body.darkTheme .ui.segment.accessRule {
border: 1px solid var(--theme_bg_secondary) !important;
}
body.darkTheme .ui.segment.accessRule:hover {
background-color: var(--theme_bg_secondary) !important;
}
body.darkTheme .ui.segment.accessRule.active {
background-color: var(--theme_bg_secondary) !important;
}
</style>
</head>
<body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<br>
<div class="ui container">
<div class="ui header">

View File

@ -8,6 +8,8 @@
<script src="../script/semantic/semantic.min.js"></script>
</head>
<body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<br>
<div class="ui container">
<div class="ui header">

View File

@ -55,10 +55,64 @@
background:#3643bb;
color:white;
}
body.darkTheme .loglist {
background-color: #1b1c1d;
color: #ffffff;
}
body.darkTheme .loglist .ui.header .content .sub.header {
color: #bbbbbb;
}
body.darkTheme .loglist .ui.divider {
border-color: #333333;
}
body.darkTheme .loglist .ui.accordion .title,
body.darkTheme .loglist .ui.accordion .content {
background-color: #1b1c1d;
color: #ffffff;
}
body.darkTheme .loglist .ui.accordion .title:hover {
background-color: #333333;
}
body.darkTheme .loglist .ui.list .item {
color: #ffffff;
}
body.darkTheme .loglist .ui.list .item .content {
color: #ffffff;
}
body.darkTheme .loglist .ui.list .item .showing {
color: #ffffff;
}
body.darkTheme .loglist .ui.button.filterbtn {
background-color: #333333;
color: #ffffff;
}
body.darkTheme .loglist .ui.button.filterbtn:hover {
background-color: #555555;
}
body.darkTheme .loglist .ui.toggle.checkbox label {
color: #ffffff;
}
body.darkTheme .loglist small {
color: #bbbbbb;
}
</style>
</head>
<body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<br>
<div class="ui container">
<div class="ui stackable grid">

View File

@ -15,6 +15,8 @@
</style>
</head>
<body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<div class="ui active inverted dimmer">
<div class="ui text loader">Loading Snippet</div>
</div>

View File

@ -15,6 +15,8 @@
</style>
</head>
<body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<br>
<div class="ui container">
<div class="ui basic segment">

View File

@ -15,6 +15,8 @@
</style>
</head>
<body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<br>
<div class="ui container">
<div class="ui basic segment">

View File

@ -56,6 +56,8 @@
</style>
</head>
<body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<br>
<div class="ui container">
<div class="ui header">

View File

@ -6,7 +6,7 @@
*/
/*
Color definations
Color definition
*/
:root{
--dark_theme_toggle: #333333;
@ -1252,6 +1252,10 @@ code{
color: var(--um_file_txt) !important;
}
.darkTheme .filename{
color:var(--text_color) !important;
}
/*
Hot Search Related

View File

@ -32,6 +32,8 @@
</style>
</head>
<body class="whiteTheme">
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<div id="navibar" class="navibar">
<!-- File Opr Group-->
<button class="fileOprBtn" title="Open" onclick="openViaButton(event);"><img class="opricon" src="img/opr/open.svg"><p class="oprtxt" locale="fileopr/Open">Open</p></button>
@ -60,7 +62,7 @@
<div id="pathDisplayField" class="ui breadcrumb addressText pathDisplay desktopOnly" >
</div>
<button id="togglePropertiesViewBtn" style="margin-left: 0.4em;" class="ui icon tiny button videmode propbar" title="Show Properties" onclick="togglePropertiesView(this);"><i class="columns icon"></i></button>
<button id="togglePropertiesViewBtn" style="margin-left: 0.4em; " class="ui icon tiny button videmode propbar" title="Show Properties" onclick="togglePropertiesView(this);"><i class="columns icon"></i></button>
</div>
<div class="msgbox" style="z-index:999; display:none; padding-bottom: 1em;">
@ -79,7 +81,7 @@
</div>
<br>
</div>
<div id="propertiesView" class="small">
<div id="propertiesView" class="small" style="height: 100%;">
<h3 class="ui header" style="margin-top: 0.4em;">
<span class="filename" style="word-break: break-all;" locale="sidebar/default/nofileselected">No File Selected</span>
<div class="sub header vpath" style="word-break: break-all;" locale="sidebar/default/instruction">Select a file to view file properties</div>
@ -471,11 +473,12 @@
</span>
</div>`);
}else{
let isDarkTheme = $("body").hasClass("darkTheme");
let extension = "." + filename.split(".").pop();
let fileIcon = getFileIcon(extension);
$("#fileList").append(`<div class="fileObject item" draggable="true" filename="${filename}" filepath="${path + filename}" ondblclick="openthis(this,event);" type="file">
<span style="display:inline-block !important;word-break: break-all; width:100%;" class="normal object">
<i class="${fileIcon} icon" style="margin-right:12px; color:grey;"></i> <span class="filename">${filename} (${humanFileSize(filesize)})</span>
<i class="${fileIcon} icon" style="margin-right:12px; color:${isDarkTheme?'white':'grey'} !important;"></i> <span class="filename">${filename} (${humanFileSize(filesize)})</span>
</span>
</div>`);
}

View File

@ -17,6 +17,8 @@
<link rel="stylesheet" href="../main.css">
</head>
<body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<br>
<div class="ui container">
<div class="ui yellow message">

View File

@ -21,6 +21,8 @@
</style>
</head>
<body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<div class="ui container">
<br>
<div class="ui segment">

View File

@ -22,8 +22,10 @@
</style>
</head>
<body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<div id="mdns-hosts">
<span><i class="ui loading spinner icon"></i> Loading</span>
</div>
<br>
<div class="ui container">

View File

@ -18,6 +18,8 @@
</head>
</head>
<body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<div class="ui container">
<br>
<div class="ui segment">

View File

@ -33,6 +33,8 @@
</style>
</head>
<body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<div id="loadingUI">
<div style="margin-top: 2em; margin-left: 2em; color: white;">
<i class="ui loading spinner icon"></i> <b>Creating virtual terminal session</b><br>

View File

@ -18,11 +18,9 @@ package main
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"
"time"
"imuslab.com/zoraxy/mod/dynamicproxy"
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
@ -32,39 +30,6 @@ import (
"imuslab.com/zoraxy/mod/wakeonlan"
)
/*
Proxy Utils
*/
//Check if site support TLS
func HandleCheckSiteSupportTLS(w http.ResponseWriter, r *http.Request) {
targetURL, err := utils.PostPara(r, "url")
if err != nil {
utils.SendErrorResponse(w, "invalid url given")
return
}
httpsUrl := fmt.Sprintf("https://%s", targetURL)
httpUrl := fmt.Sprintf("http://%s", targetURL)
client := http.Client{Timeout: 5 * time.Second}
resp, err := client.Head(httpsUrl)
if err == nil && resp.StatusCode == http.StatusOK {
js, _ := json.Marshal("https")
utils.SendJSONResponse(w, string(js))
return
}
resp, err = client.Head(httpUrl)
if err == nil && resp.StatusCode == http.StatusOK {
js, _ := json.Marshal("http")
utils.SendJSONResponse(w, string(js))
return
}
utils.SendErrorResponse(w, "invalid url given")
}
/*
Statistic Summary
*/
@ -367,12 +332,22 @@ func HandleZoraxyInfo(w http.ResponseWriter, r *http.Request) {
ZerotierConnected bool
}
displayUUID := nodeUUID
displayAllowSSHLB := *allowSshLoopback
displayBootTime := bootTime
if !authAgent.CheckAuth(r) {
displayUUID = "Unauthorized"
displayAllowSSHLB = false
displayBootTime = 0
}
info := ZoraxyInfo{
Version: version,
NodeUUID: nodeUUID,
Development: development,
BootTime: bootTime,
EnableSshLoopback: *allowSshLoopback,
Version: SYSTEM_VERSION,
NodeUUID: displayUUID,
Development: DEVELOPMENT_BUILD,
BootTime: displayBootTime,
EnableSshLoopback: displayAllowSSHLB,
ZerotierConnected: ganManager.ControllerID != "",
}