Updates v2.6.1

+ Added reverse proxy TLS skip verification
+ Added basic auth
+ Edit proxy settings
+ Whitelist
+ TCP Proxy (experimental)
+ Info (Utilities page)
This commit is contained in:
Toby Chui 2023-05-31 22:22:47 +08:00
parent 5952a1b55f
commit 20fd8e9a49
42 changed files with 87636 additions and 1165 deletions

195
src/accesslist.go Normal file
View File

@ -0,0 +1,195 @@
package main
import (
"encoding/json"
"net/http"
"imuslab.com/zoraxy/mod/utils"
)
/*
accesslist.go
This script file is added to extend the
reverse proxy function to include
banning / whitelist a specific IP address or country code
*/
/*
Blacklist Related
*/
// List a of blacklisted ip address or country code
func handleListBlacklisted(w http.ResponseWriter, r *http.Request) {
bltype, err := utils.GetPara(r, "type")
if err != nil {
bltype = "country"
}
resulst := []string{}
if bltype == "country" {
resulst = geodbStore.GetAllBlacklistedCountryCode()
} else if bltype == "ip" {
resulst = geodbStore.GetAllBlacklistedIp()
}
js, _ := json.Marshal(resulst)
utils.SendJSONResponse(w, string(js))
}
func handleCountryBlacklistAdd(w http.ResponseWriter, r *http.Request) {
countryCode, err := utils.PostPara(r, "cc")
if err != nil {
utils.SendErrorResponse(w, "invalid or empty country code")
return
}
geodbStore.AddCountryCodeToBlackList(countryCode)
utils.SendOK(w)
}
func handleCountryBlacklistRemove(w http.ResponseWriter, r *http.Request) {
countryCode, err := utils.PostPara(r, "cc")
if err != nil {
utils.SendErrorResponse(w, "invalid or empty country code")
return
}
geodbStore.RemoveCountryCodeFromBlackList(countryCode)
utils.SendOK(w)
}
func handleIpBlacklistAdd(w http.ResponseWriter, r *http.Request) {
ipAddr, err := utils.PostPara(r, "ip")
if err != nil {
utils.SendErrorResponse(w, "invalid or empty ip address")
return
}
geodbStore.AddIPToBlackList(ipAddr)
}
func handleIpBlacklistRemove(w http.ResponseWriter, r *http.Request) {
ipAddr, err := utils.PostPara(r, "ip")
if err != nil {
utils.SendErrorResponse(w, "invalid or empty ip address")
return
}
geodbStore.RemoveIPFromBlackList(ipAddr)
utils.SendOK(w)
}
func handleBlacklistEnable(w http.ResponseWriter, r *http.Request) {
enable, err := utils.PostPara(r, "enable")
if err != nil {
//Return the current enabled state
currentEnabled := geodbStore.BlacklistEnabled
js, _ := json.Marshal(currentEnabled)
utils.SendJSONResponse(w, string(js))
} else {
if enable == "true" {
geodbStore.ToggleBlacklist(true)
} else if enable == "false" {
geodbStore.ToggleBlacklist(false)
} else {
utils.SendErrorResponse(w, "invalid enable state: only true and false is accepted")
return
}
utils.SendOK(w)
}
}
/*
Whitelist Related
*/
func handleListWhitelisted(w http.ResponseWriter, r *http.Request) {
bltype, err := utils.GetPara(r, "type")
if err != nil {
bltype = "country"
}
resulst := []string{}
if bltype == "country" {
resulst = geodbStore.GetAllWhitelistedCountryCode()
} else if bltype == "ip" {
resulst = geodbStore.GetAllWhitelistedIp()
}
js, _ := json.Marshal(resulst)
utils.SendJSONResponse(w, string(js))
}
func handleCountryWhitelistAdd(w http.ResponseWriter, r *http.Request) {
countryCode, err := utils.PostPara(r, "cc")
if err != nil {
utils.SendErrorResponse(w, "invalid or empty country code")
return
}
geodbStore.AddCountryCodeToWhitelist(countryCode)
utils.SendOK(w)
}
func handleCountryWhitelistRemove(w http.ResponseWriter, r *http.Request) {
countryCode, err := utils.PostPara(r, "cc")
if err != nil {
utils.SendErrorResponse(w, "invalid or empty country code")
return
}
geodbStore.RemoveCountryCodeFromWhitelist(countryCode)
utils.SendOK(w)
}
func handleIpWhitelistAdd(w http.ResponseWriter, r *http.Request) {
ipAddr, err := utils.PostPara(r, "ip")
if err != nil {
utils.SendErrorResponse(w, "invalid or empty ip address")
return
}
geodbStore.AddIPToWhiteList(ipAddr)
}
func handleIpWhitelistRemove(w http.ResponseWriter, r *http.Request) {
ipAddr, err := utils.PostPara(r, "ip")
if err != nil {
utils.SendErrorResponse(w, "invalid or empty ip address")
return
}
geodbStore.RemoveIPFromWhiteList(ipAddr)
utils.SendOK(w)
}
func handleWhitelistEnable(w http.ResponseWriter, r *http.Request) {
enable, err := utils.PostPara(r, "enable")
if err != nil {
//Return the current enabled state
currentEnabled := geodbStore.WhitelistEnabled
js, _ := json.Marshal(currentEnabled)
utils.SendJSONResponse(w, string(js))
} else {
if enable == "true" {
geodbStore.ToggleWhitelist(true)
} else if enable == "false" {
geodbStore.ToggleWhitelist(false)
} else {
utils.SendErrorResponse(w, "invalid enable state: only true and false is accepted")
return
}
utils.SendOK(w)
}
}

View File

@ -45,7 +45,9 @@ func initAPIs() {
authRouter.HandleFunc("/api/proxy/add", ReverseProxyHandleAddEndpoint) authRouter.HandleFunc("/api/proxy/add", ReverseProxyHandleAddEndpoint)
authRouter.HandleFunc("/api/proxy/status", ReverseProxyStatus) authRouter.HandleFunc("/api/proxy/status", ReverseProxyStatus)
authRouter.HandleFunc("/api/proxy/list", ReverseProxyList) authRouter.HandleFunc("/api/proxy/list", ReverseProxyList)
authRouter.HandleFunc("/api/proxy/edit", ReverseProxyHandleEditEndpoint)
authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint) authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint)
authRouter.HandleFunc("/api/proxy/updateCredentials", UpdateProxyBasicAuthCredentials)
authRouter.HandleFunc("/api/proxy/tlscheck", HandleCheckSiteSupportTLS) authRouter.HandleFunc("/api/proxy/tlscheck", HandleCheckSiteSupportTLS)
authRouter.HandleFunc("/api/proxy/setIncoming", HandleIncomingPortSet) authRouter.HandleFunc("/api/proxy/setIncoming", HandleIncomingPortSet)
authRouter.HandleFunc("/api/proxy/useHttpsRedirect", HandleUpdateHttpsRedirect) authRouter.HandleFunc("/api/proxy/useHttpsRedirect", HandleUpdateHttpsRedirect)
@ -71,6 +73,14 @@ func initAPIs() {
authRouter.HandleFunc("/api/blacklist/ip/remove", handleIpBlacklistRemove) authRouter.HandleFunc("/api/blacklist/ip/remove", handleIpBlacklistRemove)
authRouter.HandleFunc("/api/blacklist/enable", handleBlacklistEnable) authRouter.HandleFunc("/api/blacklist/enable", handleBlacklistEnable)
//Whitelist APIs
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)
//Statistic & uptime monitoring API //Statistic & uptime monitoring API
authRouter.HandleFunc("/api/stats/summary", statisticCollector.HandleTodayStatLoad) authRouter.HandleFunc("/api/stats/summary", statisticCollector.HandleTodayStatLoad)
authRouter.HandleFunc("/api/stats/countries", HandleCountryDistrSummary) authRouter.HandleFunc("/api/stats/countries", HandleCountryDistrSummary)
@ -97,6 +107,9 @@ func initAPIs() {
authRouter.HandleFunc("/api/tcpprox/config/add", tcpProxyManager.HandleAddProxyConfig) authRouter.HandleFunc("/api/tcpprox/config/add", tcpProxyManager.HandleAddProxyConfig)
authRouter.HandleFunc("/api/tcpprox/config/edit", tcpProxyManager.HandleEditProxyConfigs) authRouter.HandleFunc("/api/tcpprox/config/edit", tcpProxyManager.HandleEditProxyConfigs)
authRouter.HandleFunc("/api/tcpprox/config/list", tcpProxyManager.HandleListConfigs) authRouter.HandleFunc("/api/tcpprox/config/list", tcpProxyManager.HandleListConfigs)
authRouter.HandleFunc("/api/tcpprox/config/start", tcpProxyManager.HandleStartProxy)
authRouter.HandleFunc("/api/tcpprox/config/stop", tcpProxyManager.HandleStopProxy)
authRouter.HandleFunc("/api/tcpprox/config/delete", tcpProxyManager.HandleRemoveProxy)
authRouter.HandleFunc("/api/tcpprox/config/status", tcpProxyManager.HandleGetProxyStatus) authRouter.HandleFunc("/api/tcpprox/config/status", tcpProxyManager.HandleGetProxyStatus)
authRouter.HandleFunc("/api/tcpprox/config/validate", tcpProxyManager.HandleConfigValidate) authRouter.HandleFunc("/api/tcpprox/config/validate", tcpProxyManager.HandleConfigValidate)
@ -123,6 +136,9 @@ func initAPIs() {
http.HandleFunc("/api/account/reset", HandleAdminAccountResetEmail) http.HandleFunc("/api/account/reset", HandleAdminAccountResetEmail)
http.HandleFunc("/api/account/new", HandleNewPasswordSetup) http.HandleFunc("/api/account/new", HandleNewPasswordSetup)
//Others
http.HandleFunc("/api/info/x", HandleZoraxyInfo)
//If you got APIs to add, append them here //If you got APIs to add, append them here
} }

View File

@ -1,102 +0,0 @@
package main
import (
"encoding/json"
"net/http"
"imuslab.com/zoraxy/mod/utils"
)
/*
blacklist.go
This script file is added to extend the
reverse proxy function to include
banning a specific IP address or country code
*/
//List a of blacklisted ip address or country code
func handleListBlacklisted(w http.ResponseWriter, r *http.Request) {
bltype, err := utils.GetPara(r, "type")
if err != nil {
bltype = "country"
}
resulst := []string{}
if bltype == "country" {
resulst = geodbStore.GetAllBlacklistedCountryCode()
} else if bltype == "ip" {
resulst = geodbStore.GetAllBlacklistedIp()
}
js, _ := json.Marshal(resulst)
utils.SendJSONResponse(w, string(js))
}
func handleCountryBlacklistAdd(w http.ResponseWriter, r *http.Request) {
countryCode, err := utils.PostPara(r, "cc")
if err != nil {
utils.SendErrorResponse(w, "invalid or empty country code")
return
}
geodbStore.AddCountryCodeToBlackList(countryCode)
utils.SendOK(w)
}
func handleCountryBlacklistRemove(w http.ResponseWriter, r *http.Request) {
countryCode, err := utils.PostPara(r, "cc")
if err != nil {
utils.SendErrorResponse(w, "invalid or empty country code")
return
}
geodbStore.RemoveCountryCodeFromBlackList(countryCode)
utils.SendOK(w)
}
func handleIpBlacklistAdd(w http.ResponseWriter, r *http.Request) {
ipAddr, err := utils.PostPara(r, "ip")
if err != nil {
utils.SendErrorResponse(w, "invalid or empty ip address")
return
}
geodbStore.AddIPToBlackList(ipAddr)
}
func handleIpBlacklistRemove(w http.ResponseWriter, r *http.Request) {
ipAddr, err := utils.PostPara(r, "ip")
if err != nil {
utils.SendErrorResponse(w, "invalid or empty ip address")
return
}
geodbStore.RemoveIPFromBlackList(ipAddr)
utils.SendOK(w)
}
func handleBlacklistEnable(w http.ResponseWriter, r *http.Request) {
enable, err := utils.PostPara(r, "enable")
if err != nil {
//Return the current enabled state
currentEnabled := geodbStore.Enabled
js, _ := json.Marshal(currentEnabled)
utils.SendJSONResponse(w, string(js))
} else {
if enable == "true" {
geodbStore.ToggleBlacklist(true)
} else if enable == "false" {
geodbStore.ToggleBlacklist(false)
} else {
utils.SendErrorResponse(w, "invalid enable state: only true and false is accepted")
return
}
utils.SendOK(w)
}
}

View File

@ -38,9 +38,10 @@ var ztAuthToken = flag.String("ztauth", "", "ZeroTier authtoken for the local no
var ztAPIPort = flag.Int("ztport", 9993, "ZeroTier controller API port") var ztAPIPort = flag.Int("ztport", 9993, "ZeroTier controller API port")
var ( var (
name = "Zoraxy" name = "Zoraxy"
version = "2.6" version = "2.6.1"
nodeUUID = "generic" nodeUUID = "generic"
development = false //Set this to false to use embedded web fs development = true //Set this to false to use embedded web fs
bootTime = time.Now().Unix()
/* /*
Binary Embedding File System Binary Embedding File System
@ -56,7 +57,7 @@ var (
authAgent *auth.AuthAgent //Authentication agent authAgent *auth.AuthAgent //Authentication agent
tlsCertManager *tlscert.Manager //TLS / SSL management tlsCertManager *tlscert.Manager //TLS / SSL management
redirectTable *redirection.RuleTable //Handle special redirection rule sets redirectTable *redirection.RuleTable //Handle special redirection rule sets
geodbStore *geodb.Store //GeoIP database geodbStore *geodb.Store //GeoIP database, also handle black list and whitelist features
netstatBuffers *netstat.NetStatBuffers //Realtime graph buffers netstatBuffers *netstat.NetStatBuffers //Realtime graph buffers
statisticCollector *statistic.Collector //Collecting statistic from visitors statisticCollector *statistic.Collector //Collecting statistic from visitors
uptimeMonitor *uptime.Monitor //Uptime monitor service worker uptimeMonitor *uptime.Monitor //Uptime monitor service worker

View File

@ -25,6 +25,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
/* /*
General Access Check General Access Check
*/ */
//Check if this ip is in blacklist //Check if this ip is in blacklist
clientIpAddr := geodb.GetRequesterIP(r) clientIpAddr := geodb.GetRequesterIP(r)
if h.Parent.Option.GeodbStore.IsBlacklisted(clientIpAddr) { if h.Parent.Option.GeodbStore.IsBlacklisted(clientIpAddr) {
@ -40,6 +41,20 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return return
} }
//Check if this ip is in whitelist
if !h.Parent.Option.GeodbStore.IsWhitelisted(clientIpAddr) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusForbidden)
template, err := os.ReadFile("./web/forbidden.html")
if err != nil {
w.Write([]byte("403 - Forbidden"))
} else {
w.Write(template)
}
h.logRequest(r, false, 403, "whitelist", "")
return
}
/* /*
Redirection Routing Redirection Routing
*/ */

View File

@ -242,10 +242,44 @@ func (router *Router) AddVirtualDirectoryProxyService(options *VdirOptions) erro
router.ProxyEndpoints.Store(options.RootName, &endpointObject) router.ProxyEndpoints.Store(options.RootName, &endpointObject)
log.Println("Adding Proxy Rule: ", options.RootName+" to "+domain) log.Println("Registered Proxy Rule: ", options.RootName+" to "+domain)
return nil return nil
} }
/*
Load routing from RP
*/
func (router *Router) LoadProxy(ptype string, key string) (*ProxyEndpoint, error) {
if ptype == "vdir" {
proxy, ok := router.ProxyEndpoints.Load(key)
if !ok {
return nil, errors.New("target proxy not found")
}
return proxy.(*ProxyEndpoint), nil
} else if ptype == "subd" {
proxy, ok := router.SubdomainEndpoint.Load(key)
if !ok {
return nil, errors.New("target proxy not found")
}
return proxy.(*ProxyEndpoint), nil
}
return nil, errors.New("unsupported ptype")
}
/*
Save routing from RP
*/
func (router *Router) SaveProxy(ptype string, key string, newConfig *ProxyEndpoint) {
if ptype == "vdir" {
router.ProxyEndpoints.Store(key, newConfig)
} else if ptype == "subd" {
router.SubdomainEndpoint.Store(key, newConfig)
}
}
/* /*
Remove routing from RP Remove routing from RP
*/ */

View File

@ -75,7 +75,7 @@ func (h *ProxyHandler) subdomainRequest(w http.ResponseWriter, r *http.Request,
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + requestURL) u, _ = url.Parse("wss://" + wsRedirectionEndpoint + requestURL)
} }
h.logRequest(r, true, 101, "subdomain-websocket", target.Domain) h.logRequest(r, true, 101, "subdomain-websocket", target.Domain)
wspHandler := websocketproxy.NewProxy(u) wspHandler := websocketproxy.NewProxy(u, target.SkipCertValidations)
wspHandler.ServeHTTP(w, r) wspHandler.ServeHTTP(w, r)
return return
} }
@ -128,7 +128,7 @@ func (h *ProxyHandler) proxyRequest(w http.ResponseWriter, r *http.Request, targ
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + r.URL.String()) u, _ = url.Parse("wss://" + wsRedirectionEndpoint + r.URL.String())
} }
h.logRequest(r, true, 101, "vdir-websocket", target.Domain) h.logRequest(r, true, 101, "vdir-websocket", target.Domain)
wspHandler := websocketproxy.NewProxy(u) wspHandler := websocketproxy.NewProxy(u, target.SkipCertValidations)
wspHandler.ServeHTTP(w, r) wspHandler.ServeHTTP(w, r)
return return
} }

View File

@ -3,7 +3,6 @@ package geodb
import ( import (
_ "embed" _ "embed"
"log" "log"
"net"
"net/http" "net/http"
"strings" "strings"
@ -11,14 +10,23 @@ import (
) )
//go:embed geoipv4.csv //go:embed geoipv4.csv
var geoipv4 []byte //Original embedded csv file var geoipv4 []byte //Geodb dataset for ipv4
//go:embed geoipv6.csv
var geoipv6 []byte //Geodb dataset for ipv6
type Store struct { type Store struct {
Enabled bool BlacklistEnabled bool
geodb [][]string //Parsed geodb list WhitelistEnabled bool
geodb [][]string //Parsed geodb list
geodbIpv6 [][]string //Parsed geodb list for ipv6
geotrie *trie
geotrieIpv6 *trie
//geoipCache sync.Map //geoipCache sync.Map
geotrie *trie
sysdb *database.Database sysdb *database.Database
} }
type CountryInfo struct { type CountryInfo struct {
@ -32,7 +40,13 @@ func NewGeoDb(sysdb *database.Database) (*Store, error) {
return nil, err return nil, err
} }
parsedGeoDataIpv6, err := parseCSV(geoipv6)
if err != nil {
return nil, err
}
blacklistEnabled := false blacklistEnabled := false
whitelistEnabled := false
if sysdb != nil { if sysdb != nil {
err = sysdb.NewTable("blacklist-cn") err = sysdb.NewTable("blacklist-cn")
if err != nil { if err != nil {
@ -44,27 +58,46 @@ func NewGeoDb(sysdb *database.Database) (*Store, error) {
return nil, err return nil, err
} }
err = sysdb.NewTable("blacklist") err = sysdb.NewTable("whitelist-cn")
if err != nil { if err != nil {
return nil, err return nil, err
} }
sysdb.Read("blacklist", "enabled", &blacklistEnabled)
err = sysdb.NewTable("whitelist-ip")
if err != nil {
return nil, err
}
err = sysdb.NewTable("blackwhitelist")
if err != nil {
return nil, err
}
sysdb.Read("blackwhitelist", "blacklistEnabled", &blacklistEnabled)
sysdb.Read("blackwhitelist", "whitelistEnabled", &whitelistEnabled)
} else { } else {
log.Println("Database pointer set to nil: Entering debug mode") log.Println("Database pointer set to nil: Entering debug mode")
} }
return &Store{ return &Store{
Enabled: blacklistEnabled, BlacklistEnabled: blacklistEnabled,
geodb: parsedGeoData, WhitelistEnabled: whitelistEnabled,
//geoipCache: sync.Map{}, geodb: parsedGeoData,
geotrie: constrctTrieTree(parsedGeoData), geotrie: constrctTrieTree(parsedGeoData),
sysdb: sysdb, geodbIpv6: parsedGeoDataIpv6,
geotrieIpv6: constrctTrieTree(parsedGeoDataIpv6),
sysdb: sysdb,
}, nil }, nil
} }
func (s *Store) ToggleBlacklist(enabled bool) { func (s *Store) ToggleBlacklist(enabled bool) {
s.sysdb.Write("blacklist", "enabled", enabled) s.sysdb.Write("blackwhitelist", "blacklistEnabled", enabled)
s.Enabled = enabled s.BlacklistEnabled = enabled
}
func (s *Store) ToggleWhitelist(enabled bool) {
s.sysdb.Write("blackwhitelist", "whitelistEnabled", enabled)
s.WhitelistEnabled = enabled
} }
func (s *Store) ResolveCountryCodeFromIP(ipstring string) (*CountryInfo, error) { func (s *Store) ResolveCountryCodeFromIP(ipstring string) (*CountryInfo, error) {
@ -79,6 +112,10 @@ func (s *Store) Close() {
} }
/*
Country code based black / white list
*/
func (s *Store) AddCountryCodeToBlackList(countryCode string) { func (s *Store) AddCountryCodeToBlackList(countryCode string) {
countryCode = strings.ToLower(countryCode) countryCode = strings.ToLower(countryCode)
s.sysdb.Write("blacklist-cn", countryCode, true) s.sysdb.Write("blacklist-cn", countryCode, true)
@ -89,6 +126,16 @@ func (s *Store) RemoveCountryCodeFromBlackList(countryCode string) {
s.sysdb.Delete("blacklist-cn", countryCode) s.sysdb.Delete("blacklist-cn", countryCode)
} }
func (s *Store) AddCountryCodeToWhitelist(countryCode string) {
countryCode = strings.ToLower(countryCode)
s.sysdb.Write("whitelist-cn", countryCode, true)
}
func (s *Store) RemoveCountryCodeFromWhitelist(countryCode string) {
countryCode = strings.ToLower(countryCode)
s.sysdb.Delete("whitelist-cn", countryCode)
}
func (s *Store) IsCountryCodeBlacklisted(countryCode string) bool { func (s *Store) IsCountryCodeBlacklisted(countryCode string) bool {
countryCode = strings.ToLower(countryCode) countryCode = strings.ToLower(countryCode)
var isBlacklisted bool = false var isBlacklisted bool = false
@ -96,6 +143,13 @@ func (s *Store) IsCountryCodeBlacklisted(countryCode string) bool {
return isBlacklisted return isBlacklisted
} }
func (s *Store) IsCountryCodeWhitelisted(countryCode string) bool {
countryCode = strings.ToLower(countryCode)
var isWhitelisted bool = false
s.sysdb.Read("whitelist-cn", countryCode, &isWhitelisted)
return isWhitelisted
}
func (s *Store) GetAllBlacklistedCountryCode() []string { func (s *Store) GetAllBlacklistedCountryCode() []string {
bannedCountryCodes := []string{} bannedCountryCodes := []string{}
entries, err := s.sysdb.ListTable("blacklist-cn") entries, err := s.sysdb.ListTable("blacklist-cn")
@ -110,6 +164,24 @@ func (s *Store) GetAllBlacklistedCountryCode() []string {
return bannedCountryCodes return bannedCountryCodes
} }
func (s *Store) GetAllWhitelistedCountryCode() []string {
whitelistedCountryCode := []string{}
entries, err := s.sysdb.ListTable("whitelist-cn")
if err != nil {
return whitelistedCountryCode
}
for _, keypairs := range entries {
ip := string(keypairs[0])
whitelistedCountryCode = append(whitelistedCountryCode, ip)
}
return whitelistedCountryCode
}
/*
IP based black / whitelist
*/
func (s *Store) AddIPToBlackList(ipAddr string) { func (s *Store) AddIPToBlackList(ipAddr string) {
s.sysdb.Write("blacklist-ip", ipAddr, true) s.sysdb.Write("blacklist-ip", ipAddr, true)
} }
@ -118,6 +190,14 @@ func (s *Store) RemoveIPFromBlackList(ipAddr string) {
s.sysdb.Delete("blacklist-ip", ipAddr) s.sysdb.Delete("blacklist-ip", ipAddr)
} }
func (s *Store) AddIPToWhiteList(ipAddr string) {
s.sysdb.Write("whitelist-ip", ipAddr, true)
}
func (s *Store) RemoveIPFromWhiteList(ipAddr string) {
s.sysdb.Delete("whitelist-ip", ipAddr)
}
func (s *Store) IsIPBlacklisted(ipAddr string) bool { func (s *Store) IsIPBlacklisted(ipAddr string) bool {
var isBlacklisted bool = false var isBlacklisted bool = false
s.sysdb.Read("blacklist-ip", ipAddr, &isBlacklisted) s.sysdb.Read("blacklist-ip", ipAddr, &isBlacklisted)
@ -142,6 +222,30 @@ func (s *Store) IsIPBlacklisted(ipAddr string) bool {
return false return false
} }
func (s *Store) IsIPWhitelisted(ipAddr string) bool {
var isBlacklisted bool = false
s.sysdb.Read("whitelist-ip", ipAddr, &isBlacklisted)
if isBlacklisted {
return true
}
//Check for IP wildcard and CIRD rules
AllBlacklistedIps := s.GetAllBlacklistedIp()
for _, blacklistRule := range AllBlacklistedIps {
wildcardMatch := MatchIpWildcard(ipAddr, blacklistRule)
if wildcardMatch {
return true
}
cidrMatch := MatchIpCIDR(ipAddr, blacklistRule)
if cidrMatch {
return true
}
}
return false
}
func (s *Store) GetAllBlacklistedIp() []string { func (s *Store) GetAllBlacklistedIp() []string {
bannedIps := []string{} bannedIps := []string{}
entries, err := s.sysdb.ListTable("blacklist-ip") entries, err := s.sysdb.ListTable("blacklist-ip")
@ -157,9 +261,27 @@ func (s *Store) GetAllBlacklistedIp() []string {
return bannedIps return bannedIps
} }
// Check if a IP address is blacklisted, in either country or IP blacklist func (s *Store) GetAllWhitelistedIp() []string {
whitelistedIp := []string{}
entries, err := s.sysdb.ListTable("whitelist-ip")
if err != nil {
return whitelistedIp
}
for _, keypairs := range entries {
ip := string(keypairs[0])
whitelistedIp = append(whitelistedIp, ip)
}
return whitelistedIp
}
/*
Check if a IP address is blacklisted, in either country or IP blacklist
IsBlacklisted default return is false (allow access)
*/
func (s *Store) IsBlacklisted(ipAddr string) bool { func (s *Store) IsBlacklisted(ipAddr string) bool {
if !s.Enabled { if !s.BlacklistEnabled {
//Blacklist not enabled. Always return false //Blacklist not enabled. Always return false
return false return false
} }
@ -185,6 +307,40 @@ func (s *Store) IsBlacklisted(ipAddr string) bool {
return false return false
} }
/*
IsWhitelisted check if a given IP address is in the current
server's white list.
Note that the Whitelist default result is true even
when encountered error
*/
func (s *Store) IsWhitelisted(ipAddr string) bool {
if !s.WhitelistEnabled {
//Whitelist not enabled. Always return true (allow access)
return true
}
if ipAddr == "" {
//Unable to get the target IP address, assume ok
return true
}
countryCode, err := s.ResolveCountryCodeFromIP(ipAddr)
if err != nil {
return true
}
if s.IsCountryCodeWhitelisted(countryCode.CountryIsoCode) {
return true
}
if s.IsIPWhitelisted(ipAddr) {
return true
}
return false
}
func (s *Store) GetRequesterCountryISOCode(r *http.Request) string { func (s *Store) GetRequesterCountryISOCode(r *http.Request) string {
ipAddr := GetRequesterIP(r) ipAddr := GetRequesterIP(r)
if ipAddr == "" { if ipAddr == "" {
@ -197,54 +353,3 @@ func (s *Store) GetRequesterCountryISOCode(r *http.Request) string {
return countryCode.CountryIsoCode return countryCode.CountryIsoCode
} }
// Utilities function
func GetRequesterIP(r *http.Request) string {
ip := r.Header.Get("X-Forwarded-For")
if ip == "" {
ip = r.Header.Get("X-Real-IP")
if ip == "" {
ip = strings.Split(r.RemoteAddr, ":")[0]
}
}
return ip
}
// Match the IP address with a wildcard string
func MatchIpWildcard(ipAddress, wildcard string) bool {
// Split IP address and wildcard into octets
ipOctets := strings.Split(ipAddress, ".")
wildcardOctets := strings.Split(wildcard, ".")
// Check that both have 4 octets
if len(ipOctets) != 4 || len(wildcardOctets) != 4 {
return false
}
// Check each octet to see if it matches the wildcard or is an exact match
for i := 0; i < 4; i++ {
if wildcardOctets[i] == "*" {
continue
}
if ipOctets[i] != wildcardOctets[i] {
return false
}
}
return true
}
// Match ip address with CIDR
func MatchIpCIDR(ip string, cidr string) bool {
// parse the CIDR string
_, cidrnet, err := net.ParseCIDR(cidr)
if err != nil {
return false
}
// parse the IP address
ipAddr := net.ParseIP(ip)
// check if the IP address is within the CIDR range
return cidrnet.Contains(ipAddr)
}

84470
src/mod/geodb/geoipv6.csv Normal file

File diff suppressed because it is too large Load Diff

View File

@ -24,7 +24,13 @@ func (s *Store) search(ip string) string {
*/ */
//Search in geotrie tree //Search in geotrie tree
cc := s.geotrie.search(ip) cc := ""
if IsIPv6(ip) {
cc = s.geotrieIpv6.search(ip)
} else {
cc = s.geotrie.search(ip)
}
/* /*
if cc != "" { if cc != "" {
s.geoipCache.Store(ip, cc) s.geoipCache.Store(ip, cc)

115
src/mod/geodb/netutils.go Normal file
View File

@ -0,0 +1,115 @@
package geodb
import (
"net"
"net/http"
"strings"
)
// Utilities function
func GetRequesterIP(r *http.Request) string {
ip := r.Header.Get("X-Real-Ip")
if ip == "" {
ip = r.Header.Get("X-Forwarded-For")
}
if ip == "" {
ip = r.RemoteAddr
}
/*
Possible shits that might be extracted by this code
127.0.0.1:61001
[15c4:cbb4:cc98:4291:ffc1:3a46:06a1:51a7]:61002
127.0.0.1
158.250.160.114,109.21.249.211
[15c4:cbb4:cc98:4291:ffc1:3a46:06a1:51a7],109.21.249.211
We need to extract just the first ip address
*/
requesterRawIp := ip
if strings.Contains(requesterRawIp, ",") {
//Trim off all the forwarder IPs
requesterRawIp = strings.Split(requesterRawIp, ",")[0]
}
//Trim away the port number
reqHost, _, err := net.SplitHostPort(requesterRawIp)
if err == nil {
requesterRawIp = reqHost
}
if strings.HasPrefix(requesterRawIp, "[") && strings.HasSuffix(requesterRawIp, "]") {
//e.g. [15c4:cbb4:cc98:4291:ffc1:3a46:06a1:51a7]
requesterRawIp = requesterRawIp[1 : len(requesterRawIp)-1]
}
return requesterRawIp
}
// Match the IP address with a wildcard string
func MatchIpWildcard(ipAddress, wildcard string) bool {
// Split IP address and wildcard into octets
ipOctets := strings.Split(ipAddress, ".")
wildcardOctets := strings.Split(wildcard, ".")
// Check that both have 4 octets
if len(ipOctets) != 4 || len(wildcardOctets) != 4 {
return false
}
// Check each octet to see if it matches the wildcard or is an exact match
for i := 0; i < 4; i++ {
if wildcardOctets[i] == "*" {
continue
}
if ipOctets[i] != wildcardOctets[i] {
return false
}
}
return true
}
// Match ip address with CIDR
func MatchIpCIDR(ip string, cidr string) bool {
// parse the CIDR string
_, cidrnet, err := net.ParseCIDR(cidr)
if err != nil {
return false
}
// parse the IP address
ipAddr := net.ParseIP(ip)
// check if the IP address is within the CIDR range
return cidrnet.Contains(ipAddr)
}
// Check if a ip is private IP range
func IsPrivateIP(ipStr string) bool {
ip := net.ParseIP(ipStr)
if ip == nil {
return false
}
return ip.IsPrivate()
}
// Check if an Ip string is ipv6
func IsIPv6(ipStr string) bool {
ip := net.ParseIP(ipStr)
if ip == nil {
return false
}
return ip.To4() == nil && ip.To16() != nil
}
// Check if an Ip string is ipv6
func IsIPv4(ipStr string) bool {
ip := net.ParseIP(ipStr)
if ip == nil {
return false
}
return ip.To4() != nil
}

View File

@ -24,6 +24,10 @@ func ipToBitString(ip string) string {
// Convert the IP address to a 4-byte slice // Convert the IP address to a 4-byte slice
ipBytes := parsedIP.To4() ipBytes := parsedIP.To4()
if ipBytes == nil {
//This is an IPv6 address
ipBytes = parsedIP.To16()
}
// Convert each byte in the IP address to its 8-bit binary representation // Convert each byte in the IP address to its 8-bit binary representation
var result []string var result []string
@ -36,23 +40,38 @@ func ipToBitString(ip string) string {
} }
func bitStringToIp(bitString string) string { func bitStringToIp(bitString string) string {
// Split the bit string into four 8-bit segments // Check if the bit string represents an IPv4 or IPv6 address
segments := []string{ isIPv4 := len(bitString) == 32
bitString[:8],
bitString[8:16], // Split the bit string into 8-bit segments
bitString[16:24], segments := make([]string, 0)
bitString[24:32], if isIPv4 {
for i := 0; i < 4; i++ {
segments = append(segments, bitString[i*8:(i+1)*8])
}
} else {
for i := 0; i < 16; i++ {
segments = append(segments, bitString[i*8:(i+1)*8])
}
} }
// Convert each segment to its decimal equivalent // Convert each segment to its decimal equivalent
var decimalSegments []int decimalSegments := make([]int, len(segments))
for _, s := range segments { for i, s := range segments {
i, _ := strconv.ParseInt(s, 2, 64) val, _ := strconv.ParseInt(s, 2, 64)
decimalSegments = append(decimalSegments, int(i)) decimalSegments[i] = int(val)
} }
// Join the decimal segments with dots to form the IP address string // Construct the IP address string based on the type (IPv4 or IPv6)
return fmt.Sprintf("%d.%d.%d.%d", decimalSegments[0], decimalSegments[1], decimalSegments[2], decimalSegments[3]) if isIPv4 {
return fmt.Sprintf("%d.%d.%d.%d", decimalSegments[0], decimalSegments[1], decimalSegments[2], decimalSegments[3])
} else {
ip := make(net.IP, net.IPv6len)
for i := 0; i < net.IPv6len; i++ {
ip[i] = byte(decimalSegments[i])
}
return ip.String()
}
} }
// inititlaizing a new trie // inititlaizing a new trie
@ -93,17 +112,11 @@ func isReservedIP(ip string) bool {
if parsedIP.IsLinkLocalUnicast() || parsedIP.IsLinkLocalMulticast() { if parsedIP.IsLinkLocalUnicast() || parsedIP.IsLinkLocalMulticast() {
return true return true
} }
// Check if the IP address is in the private address ranges
privateRanges := []*net.IPNet{ if parsedIP.IsPrivate() {
{IP: net.ParseIP("10.0.0.0"), Mask: net.CIDRMask(8, 32)}, return true
{IP: net.ParseIP("172.16.0.0"), Mask: net.CIDRMask(12, 32)},
{IP: net.ParseIP("192.168.0.0"), Mask: net.CIDRMask(16, 32)},
}
for _, r := range privateRanges {
if r.Contains(parsedIP) {
return true
}
} }
// If the IP address is not a reserved address, return false // If the IP address is not a reserved address, return false
return false return false
} }

View File

@ -29,9 +29,9 @@ import (
*/ */
/* /*
Bianry embedding Bianry embedding
Make sure when compile, gotty binary exists in static.gotty Make sure when compile, gotty binary exists in static.gotty
*/ */
var ( var (
//go:embed gotty/* //go:embed gotty/*
@ -61,7 +61,7 @@ func NewSSHProxyManager() *Manager {
} }
} }
//Get the next free port in the list // Get the next free port in the list
func (m *Manager) GetNextPort() int { func (m *Manager) GetNextPort() int {
nextPort := m.StartingPort nextPort := m.StartingPort
occupiedPort := make(map[int]bool) occupiedPort := make(map[int]bool)
@ -96,7 +96,7 @@ func (m *Manager) HandleHttpByInstanceId(instanceId string, w http.ResponseWrite
r.Header.Set("A-Upgrade", "websocket") r.Header.Set("A-Upgrade", "websocket")
requestURL = strings.TrimPrefix(requestURL, "/") requestURL = strings.TrimPrefix(requestURL, "/")
u, _ := url.Parse("ws://127.0.0.1:" + strconv.Itoa(targetInstance.AssignedPort) + "/" + requestURL) u, _ := url.Parse("ws://127.0.0.1:" + strconv.Itoa(targetInstance.AssignedPort) + "/" + requestURL)
wspHandler := websocketproxy.NewProxy(u) wspHandler := websocketproxy.NewProxy(u, false)
wspHandler.ServeHTTP(w, r) wspHandler.ServeHTTP(w, r)
return return
} }
@ -168,7 +168,7 @@ func (m *Manager) NewSSHProxy(binaryRoot string) (*Instance, error) {
return &thisInstance, nil return &thisInstance, nil
} }
//Create a new Connection to target address // Create a new Connection to target address
func (i *Instance) CreateNewConnection(listenPort int, username string, remoteIpAddr string, remotePort int) error { func (i *Instance) CreateNewConnection(listenPort int, username string, remoteIpAddr string, remotePort int) error {
//Create a gotty instance //Create a gotty instance
connAddr := remoteIpAddr connAddr := remoteIpAddr

View File

@ -145,7 +145,9 @@ func (c *Collector) RecordRequest(ri RequestInfo) {
//Filter out CF forwarded requests //Filter out CF forwarded requests
if strings.Contains(ri.IpAddr, ",") { if strings.Contains(ri.IpAddr, ",") {
ips := strings.Split(strings.TrimSpace(ri.IpAddr), ",") ips := strings.Split(strings.TrimSpace(ri.IpAddr), ",")
if len(ips) >= 1 { if len(ips) >= 1 && IsValidIPAddress(strings.TrimPrefix(ips[0], "[")) {
//Example when forwarded from CF: 158.250.160.114,109.21.249.211
//Or IPv6 [15c4:cbb4:cc98:4291:ffc1:3a46:06a1:51a7],109.21.249.211
ri.IpAddr = ips[0] ri.IpAddr = ips[0]
} }
} }

View File

@ -2,6 +2,7 @@ package statistic
import ( import (
"fmt" "fmt"
"net"
"time" "time"
) )
@ -26,3 +27,19 @@ func IsBeforeToday(dateString string) bool {
today := time.Now().UTC().Truncate(24 * time.Hour) today := time.Now().UTC().Truncate(24 * time.Hour)
return date.Before(today) || dateString == time.Now().Format(layout) return date.Before(today) || dateString == time.Now().Format(layout)
} }
// Check if the IP string is a valid ip address
func IsValidIPAddress(ip string) bool {
// Check if the string is a valid IPv4 address
if parsedIP := net.ParseIP(ip); parsedIP != nil && parsedIP.To4() != nil {
return true
}
// Check if the string is a valid IPv6 address
if parsedIP := net.ParseIP(ip); parsedIP != nil && parsedIP.To16() != nil {
return true
}
// If the string is neither a valid IPv4 nor IPv6 address, return false
return false
}

View File

@ -157,6 +157,11 @@ func (c *ProxyRelayConfig) Start() error {
return nil return nil
} }
// Stop a running proxy if running
func (c *ProxyRelayConfig) IsRunning() bool {
return c.Running || c.stopChan != nil
}
// Stop a running proxy if running // Stop a running proxy if running
func (c *ProxyRelayConfig) Stop() { func (c *ProxyRelayConfig) Stop() {
if c.Running || c.stopChan != nil { if c.Running || c.stopChan != nil {

View File

@ -122,6 +122,77 @@ func (m *Manager) HandleListConfigs(w http.ResponseWriter, r *http.Request) {
utils.SendJSONResponse(w, string(js)) utils.SendJSONResponse(w, string(js))
} }
func (m *Manager) HandleStartProxy(w http.ResponseWriter, r *http.Request) {
uuid, err := utils.PostPara(r, "uuid")
if err != nil {
utils.SendErrorResponse(w, "invalid uuid given")
return
}
targetProxyConfig, err := m.GetConfigByUUID(uuid)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
err = targetProxyConfig.Start()
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
utils.SendOK(w)
}
func (m *Manager) HandleStopProxy(w http.ResponseWriter, r *http.Request) {
uuid, err := utils.PostPara(r, "uuid")
if err != nil {
utils.SendErrorResponse(w, "invalid uuid given")
return
}
targetProxyConfig, err := m.GetConfigByUUID(uuid)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
if !targetProxyConfig.IsRunning() {
utils.SendErrorResponse(w, "target proxy service is not running")
return
}
targetProxyConfig.Stop()
utils.SendOK(w)
}
func (m *Manager) HandleRemoveProxy(w http.ResponseWriter, r *http.Request) {
uuid, err := utils.PostPara(r, "uuid")
if err != nil {
utils.SendErrorResponse(w, "invalid uuid given")
return
}
targetProxyConfig, err := m.GetConfigByUUID(uuid)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
if targetProxyConfig.IsRunning() {
utils.SendErrorResponse(w, "Service is running")
return
}
err = m.RemoveConfig(targetProxyConfig.UUID)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
utils.SendOK(w)
}
func (m *Manager) HandleGetProxyStatus(w http.ResponseWriter, r *http.Request) { func (m *Manager) HandleGetProxyStatus(w http.ResponseWriter, r *http.Request) {
uuid, err := utils.GetPara(r, "uuid") uuid, err := utils.GetPara(r, "uuid")
if err != nil { if err != nil {

View File

@ -132,10 +132,12 @@ func (m *Manager) EditConfig(configUUID string, newName string, newPortA string,
foundConfig.Timeout = newTimeout foundConfig.Timeout = newTimeout
} }
err = foundConfig.ValidateConfigs() /*
if err != nil { err = foundConfig.ValidateConfigs()
return err if err != nil {
} return err
}
*/
m.SaveConfigToDatabase() m.SaveConfigToDatabase()

View File

@ -1,34 +1,22 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
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 -----END CERTIFICATE-----
A4ICAQBVldF/qjWyGJ5TiZMiXly/So9zR3Xq7O1qayqYxb5SxvhZYCtsFVrAl6Ux
5bTZ0XQagjck2VouHOG6s98DpaslWFw9N8ADAmljQ8WL1hT5Ij1LXs2sF0FqttFf
YgoT5BOjnHZGlN+FgzAkdF91cYrfZwLm63jvAQtIHwjMSeymy2Fq8gdEZxagYuwG
gLkZxw1YG+gP778CKHT2Ff232kH+5up460aGLHLvg+xHQIWBt2FNGdv68u57hWxh
XXji4/DewQ0RdJW1JdpSg4npebDNiXpo9pKY/SxU056raOtPA94U/h12cHVkszT7
IxdFC2PszAblbSZhHKGE0C6SbATsqvK4gz6e4h7HWVuPPNWpPW2BNjvyenpijV/E
YsSe6F7uQE/I/iHp9VMcjWuwItqed9yKDeOfDH4+pidowbSJQ97xYfZge36ZEUHC
2ZdQsR0qS+t2h0KlEDN7FNxai3ikSB1bs2AjtU67ofGtoIz/HD70TT6zHKhISZgI
w/4/SY7Hd+P+AWSdJwo+ycZYZlXajqh/cxVJ0zVBr5vKC9KnJ+IjnQ/q7CLcxM4W
aAFC1jakdPz7qO+xNVLQRf8lVnPJNtI88OrlL4n02JlLS/QUSwELXFW0bOKP33jm
PIbPdeP8k0XVe9wlI7MzUQC8pCt+gQ77awTt83Nxp9Xdn1Zbqw==
-----END CERTIFICATE-----

View File

@ -1,52 +1,28 @@
-----BEGIN PRIVATE KEY----- -----BEGIN PRIVATE KEY-----
MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQCwGl/259Fg590p MIIEwQIBADANBgkqhkiG9w0BAQEFAASCBKswggSnAgEAAoIBAgDFq/dCrgMGigew
+37nUSJumqED3LDpiy3EvkE2x6XHcRNWe7u9hEiUfTKmA82cqC5n4Fw3iYkWNDfo Zb2b6vR2CMjzfaCtfZzRnowlBCgTsHrB+lQjQ7J9G6BNfJeiJFmAvG5E6CIJSanw
3hWk3vJJV84JIJnPuU+898uJv2K6QGh0FhA0DMyQpwuPKhiBLwwP31yp7uzjyTgk VotuXtGy9siYxsI+jcP9FsJPVokI1ctnRrvK9wNhzrChBbidTKSiEd9Pai1y745h
qnpvZY6afY61VkhLsApm9WxEk1uWHLsLcuQucBIoXSNv+JowZEAT5BX/POe8aD0W LpphFm5cawpMCo9LARdEy2vz3CQ+Bk+ysIPbssUcE6YhxUAEO8UpG6LAdZIeEV9T
E2BBZZSKXY56zHd6WPgrCVwc1f5z2lnPrJWAmt6uPSz3g4MS656KoAU5axmumnvn mfUBkvJh5igs1D19k+/M+mQwP4cwqLgwzCv+8sTQPBTmNJ9UWr5x3rzNWKGn039P
h98Ljk9t5M2J+VDUHdqqRO2XlsvzwRKDAx21vUkZY3kchLVOrZ3/c+Cpu29QSGCB vvJqHkQgf0JibibwByzht8vwMAvGdpI3cqYbDonfms5esm8kt2G3jY8NE6ZCtc3y
oUNt7Q7EbZXfceoR94bcGlmliEIHcjsU+lEL9Onw8AJjXc0dgH0/mFFT8Ocg2EeM 4bzAaQGIGQIDAQABAoIBARA+w8FdH66H5X3fvqdztceFjU5FgtD/Q8YOa6IXJ1wG
wIsrEeyiWGKmFa7FK40giiNEvrbOPsnCOcGByCFDNUUng7hMF94wNp0sgjed0O2y 4u/SLNwBEkgp3xC/Lo8KwbhMxBsxoKp2vVqdIjRd4on8shusKgaODA9esXVnvTdW
ZHV0htrt+D4JZZ2/hmzRcaoc5qIc1yVzsGEkXxKCJDGPGOYWHVmwmbcvE+zD+IKX qrLAI2rYxhRhsi5pk/SJefY/1cRnC3koquDdvZ5BA6zgtyXssD4PxuPGfAa8jtXy
sgsTv7VoFCUGMSPTIG8y22QQOFFsgvFcU8wAsb+pqQ+9b07tZYKUBzTd6L8kV2rZ GIPUDj/Na+pFf3u6iKGGFA7xmKA+Jx3xL77zRdiG5bS70uTUJIpbJ9fdFDTEwyb+
xqa5jrb9N/UcH5NKkZZgf5xIiqy7dXm7b2OZ1mOuOe4zsXMYSM1IKysyFItguQwm 5hy6gmiPZ96bg3LnCl23jBx1RqvZxMxm6nHkEtMStoqczCkDIqypq0GaDD3Op5P9
asMZ0VAEHfXHbij0he3Kt8pmLDEyVQIDAQABAoICAATmtwUILqujyGQCu+V0PKEX TDVnrz37FQn4PWrq2VoqoKNcigcloBd620dL3p8jVcihAoGBD5yXW+uCGWDEufD0
bKPO4J2fYga3xNjhdZu3afJePztnEx4O3foA4RgbFi+N7wMcsNQNYAD7LV8JVXT1 Dvqd5pWD4pXlIP5E6Br9KFaDV0hHEyiJrXk6BzUL0EB7w+8kFafHW2gnvIpS99Tk
HKbkYWOGpNF9lAyhZv4IDOAuPQU11fuwqoGxij0OMie+77VLEQzF7OoYVJAFI5Lp mI37v/8qGqOerrsru/KtbwXR524LsSFBMXoJ+KtEanlZu+qBvhXsRA2ov/dqO+mb
K6+gVyLEI4X6DqlZ8JKc+he3euJP/DFjZjkXkjMGl0H2dyZDa6+ytwCGSYeIbDnt XEpJbcXGMcFuzeTRoss1JpFUcOKlAoGBDKlqhldAjzpWigd29hjK8saoN3nO7XW4
oKmKR0kAcOfBuu6ShiJzUUyWYRLTPJ9c1IOPBXbhV+hDy+FtOanCYvBut6Z6r3s/ O4QjRfnU2M/4pcOZrvu3DyPRfF5et2KAfp6pyYDwFfYWpSzzwblyZQTYCaGT5MCP
gvj0F2vP6OYURQiTCdoe5YT/8TO9sOsj+Zrxlpo5+svBTd9reA2j9gulkVrd3itN e3V5ee0dCjdIkB92XGv9xzZLLWClMcoLEEV2knRVY8LdbDAQHxFgOdCkOIWNfw07
c2Ee7fyuyrCRnEcKoT6BI8/LqH5eWGQKKS9WhOz26VkrorcYYZN3g4ayv+MiiSIm +BmM72YHHhllAoGBDptOqrxQ/3mg1vBxCUrHTiT6PphMx2/f/OKzlnhLbvC7P1ug
jeo/kAWCqT5ylvlw2gaCbPjB4kbx7yMI/myjgF0R4+aNQaHpXa2qqEORitGx40M7 ZWSVPIUPRovuwMYRFwnh5s4uz6MEOclBENNXhq4xMLeCEq4hHzrRtpzVZhl6awJY
T1V2JIxnsa83TBwumunkYC2pX7bNS0a1VuCNxUafJRKEcvKhWmiRHaWddZn46G8N QviSN83Wt2BO6xlgxv8wDgRRrTrKdL//knwW89QlugvnplC/K/fBBRLY1L3ZAoGB
E56qFzSaLbkd+J71jso9llK5joGIQTt2pbKUdV9LIm5Nsbtp2VgF9URIw5RZFftx BOg3r57rF1c9qLrh4NiU9ugE05MynhbscqxwWzNKyUg4jk2zJvzI4mY4TuHoBVx4
PfSm9XM9DtWuxheO4gNwAuOvtaOxztNMvSkQzhTOggSRpt15hFd7CeBrpK43feAH fhoRpVWCNpCsEBHO2np7mij5bSogvhvev7M0hAtgINByH+EBpyn3LZieJBT7kMND
b2pMequB8MHpUieyxlwBAoIBAQC5IRbaKx+fSEbYeIySUwbN8GCJQl+wmvc1gqCC 7GdvX60UVthzpfUumkvKpj11F66yutWvMyT72OAKzCB9AoGBBHixLZSz89STQNNT
DflEQqxTvCGBB5SLHHurTT0ubhXkvbrtuS5f4IC+htzKSuwlqn3lS0aOXtFP2tT6 rYcSDW79Lj18Z6/HBhLwbteMfuYun6HUssh2CKR7awFa/UOmYySiCAV97m38hjDB
D9iiMxLxIId5l6dD+PjMWtQcWc8wUQ7+ieRgxybDqiCWMyTbvNgwlkcIbRxmcqyN JC5eMEskRGGrejddtUGjIhNX1hanAkhlnbRwVZc97XvXjryDGBZtaTN/2x4lD59t
4/LmmgzTnr5CH0DC/J7xpUJuX9LPVb4ZvBYjz5X++Yb7pCa+kXp0Z6yU48bG3sRe mKYLZqGfZ+fMnaWoxLrCnn0cjIBK
yiUKp3Z4vDoOkMLHTPvTQLG81rQuJnBUw2uLWM0kg1AwteZcQ/gH1ilVbJzMBnKm -----END PRIVATE KEY-----
mtuJWtoPnM2zIhCsURngmBN+qxOb5kchMSvPzAQBCw7HBjWpAoIBAQDzhLQO434G
XhyDcdkdMRbDZ8Q8PqtOloAbczMuPGgwHV7rVe/BvnJS7HDDebwlJBD8nhGvgBrp
CsjNGHjSQC7ydUa8dP4Aw/46izdR8DsAwqGZq+tZhkY5CS88QpflUT5rftW0RObn
Cb/gDzdxHy35/scSICxa2HwcZnqXqfEwnbjkxFwBYFSt6hRiwNhDhd6ZxKa6gt56
DS9uIxt1IhKgXZfIw1Vo0mHHFLsB7czGZ0O24ya31Es0bUWGgWIcxvKw6MqKhFWw
ncCakVg278UYUm/zt6Dcrn3XYnK7Pr944AiKO21PMQhG7Rb+OVwxgjMhk7/BCt+k
sPR1Dct5pqrNAoIBAAl2jYp9ZdJoiWaLUvQv1ks0nFqnz+hhI33SvY2oVTOODO0C
0tubnZY20IODITt8WRYmNKXuL1arTSlwD10v0z5hpqnP3T1tz1k7oGNf5/zyi2dT
+FjYza4FzgH0Kp+AX7zih9evCMOBqpOZ4KyM1Ld+wbZKGDtwCGGcPwHJwyLSgRFY
LfWHT3IoI5/KiMjHkSkUAvGh0afm9o3gB2xZibl4CkBlBEdgFUsZHASUZKxUvxOQ
247fC3XQk5bK2csDVpZ9VISgsKCg22ugYrr6sVnKB6Wu5tH9CU7MjZPCmrI8uKTP
qRwdA6krRB1c6LIy4H+5l600rD6k+Rdsj0bRJHECggEAeBXSrRzmAsHaEb/MryaL
8SR0krjcxU5WMjMm5AAJ6OAy9J5WMxZ1TgsmuF6Jt08HyWsxkXf8zTryNqGAwz2/
aPUIQtr2fu4nqjsItrFeh0tzYVJ0JpueeXXcAz1bpkvgGiZbwB/SNdCK/DTExFX5
2DQZewi+lrX2zhKDFdNKCw1cJgPm0w7r8y9hiilK/FFBqlZdWdA7Ybiq0Qci/Som
QUqmFOyua5iDeybv6U2ZE6XMsJ1ndHON+naAOIoJFePNvguuBYyorQW9+vr9o2mt
qgbNCkRdYTXy/ImhxlB1H2hrDa+sgcbOLBuyoP8sRYXNLRutDccM7iwNAMQiuQTF
aQKCAQEAiKPwUodT6LNu4lrSbsDAYIqWwlfM0wwUhudT5UTVHSYI3ap0QOiEuzOl
IJVdx+vx7rQW7l+JIL6s4shA7mzpzuTVlhRuDuGZx0qQLP7INVpCLzIEbYGI2dL7
WLhJd4eYKltJ+BG7S51tq9/6rVcUDn5DKzyGNyeGhOnaYkk+eTm483+vpOP2/ITi
cbVv3mx4qE7zMPIxIufm+c8RonadJzYiq1uMk8t0TrcW/B9RTly/Y96kamjyU5b0
OcLdRcx3ppKAxHD9AvwAR6SiuNLfNjM9KZM40zM5goMrCJJzwgb7UGeMuw2z7L9F
+iSj2pW0Rbdy7oOcFRF/iM2GwFYc1Q==
-----END PRIVATE KEY-----

View File

@ -2,6 +2,7 @@
package websocketproxy package websocketproxy
import ( import (
"crypto/tls"
"fmt" "fmt"
"io" "io"
"log" "log"
@ -46,16 +47,19 @@ type WebsocketProxy struct {
// If nil, DefaultDialer is used. // If nil, DefaultDialer is used.
Dialer *websocket.Dialer Dialer *websocket.Dialer
Verbal bool Verbal bool
SkipTlsValidation bool
} }
// ProxyHandler returns a new http.Handler interface that reverse proxies the // ProxyHandler returns a new http.Handler interface that reverse proxies the
// request to the given target. // request to the given target.
func ProxyHandler(target *url.URL) http.Handler { return NewProxy(target) } func ProxyHandler(target *url.URL, skipTlsValidation bool) http.Handler {
return NewProxy(target, skipTlsValidation)
}
// NewProxy returns a new Websocket reverse proxy that rewrites the // NewProxy returns a new Websocket reverse proxy that rewrites the
// URL's to the scheme, host and base path provider in target. // URL's to the scheme, host and base path provider in target.
func NewProxy(target *url.URL) *WebsocketProxy { func NewProxy(target *url.URL, skipTlsValidation bool) *WebsocketProxy {
backend := func(r *http.Request) *url.URL { backend := func(r *http.Request) *url.URL {
// Shallow copy // Shallow copy
u := *target u := *target
@ -64,7 +68,7 @@ func NewProxy(target *url.URL) *WebsocketProxy {
u.RawQuery = r.URL.RawQuery u.RawQuery = r.URL.RawQuery
return &u return &u
} }
return &WebsocketProxy{Backend: backend, Verbal: false} return &WebsocketProxy{Backend: backend, Verbal: false, SkipTlsValidation: skipTlsValidation}
} }
// ServeHTTP implements the http.Handler that proxies WebSocket connections. // ServeHTTP implements the http.Handler that proxies WebSocket connections.
@ -84,7 +88,15 @@ func (w *WebsocketProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
dialer := w.Dialer dialer := w.Dialer
if w.Dialer == nil { if w.Dialer == nil {
dialer = DefaultDialer if w.SkipTlsValidation {
//Disable TLS secure check if target allow skip verification
bypassDialer := websocket.DefaultDialer
bypassDialer.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
dialer = bypassDialer
} else {
//Just use the default dialer come with gorilla websocket
dialer = DefaultDialer
}
} }
// Pass headers from the incoming request to the dialer to forward them to // Pass headers from the incoming request to the dialer to forward them to

View File

@ -28,7 +28,7 @@ func TestProxy(t *testing.T) {
} }
u, _ := url.Parse(backendURL) u, _ := url.Parse(backendURL)
proxy := NewProxy(u) proxy := NewProxy(u, false)
proxy.Upgrader = upgrader proxy.Upgrader = upgrader
mux := http.NewServeMux() mux := http.NewServeMux()
@ -46,7 +46,7 @@ func TestProxy(t *testing.T) {
mux2 := http.NewServeMux() mux2 := http.NewServeMux()
mux2.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { mux2.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// Don't upgrade if original host header isn't preserved // Don't upgrade if original host header isn't preserved
if r.Host != "127.0.0.1:7777" { if r.Host != "127.0.0.1:7777" {
log.Printf("Host header set incorrectly. Expecting 127.0.0.1:7777 got %s", r.Host) log.Printf("Host header set incorrectly. Expecting 127.0.0.1:7777 got %s", r.Host)
return return
} }

View File

@ -281,23 +281,123 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
} }
utils.SendOK(w) utils.SendOK(w)
}
/*
ReverseProxyHandleEditEndpoint handles proxy endpoint edit
This endpoint do not handle
basic auth credential update. The credential
will be loaded from old config and reused
*/
func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
eptype, err := utils.PostPara(r, "type") //Support root, vdir and subd
if err != nil {
utils.SendErrorResponse(w, "type not defined")
return
}
rootNameOrMatchingDomain, err := utils.PostPara(r, "rootname")
if err != nil {
utils.SendErrorResponse(w, "Target proxy rule not defined")
return
}
endpoint, err := utils.PostPara(r, "ep")
if err != nil {
utils.SendErrorResponse(w, "endpoint not defined")
return
}
tls, _ := utils.PostPara(r, "tls")
if tls == "" {
tls = "false"
}
useTLS := (tls == "true")
stv, _ := utils.PostPara(r, "tlsval")
if stv == "" {
stv = "false"
}
skipTlsValidation := (stv == "true")
rba, _ := utils.PostPara(r, "bauth")
if rba == "" {
rba = "false"
}
requireBasicAuth := (rba == "true")
//Load the previous basic auth credentials from current proxy rules
targetProxyEntry, err := dynamicProxyRouter.LoadProxy(eptype, rootNameOrMatchingDomain)
if err != nil {
utils.SendErrorResponse(w, "Target proxy config not found or could not be loaded")
return
}
if eptype == "vdir" {
thisOption := dynamicproxy.VdirOptions{
RootName: targetProxyEntry.RootOrMatchingDomain,
Domain: endpoint,
RequireTLS: useTLS,
SkipCertValidations: skipTlsValidation,
RequireBasicAuth: requireBasicAuth,
BasicAuthCredentials: targetProxyEntry.BasicAuthCredentials,
}
dynamicProxyRouter.AddVirtualDirectoryProxyService(&thisOption)
} else if eptype == "subd" {
thisOption := dynamicproxy.SubdOptions{
MatchingDomain: targetProxyEntry.RootOrMatchingDomain,
Domain: endpoint,
RequireTLS: useTLS,
SkipCertValidations: skipTlsValidation,
RequireBasicAuth: requireBasicAuth,
BasicAuthCredentials: targetProxyEntry.BasicAuthCredentials,
}
dynamicProxyRouter.AddSubdomainRoutingService(&thisOption)
}
//Save it to file
thisProxyConfigRecord := Record{
ProxyType: eptype,
Rootname: targetProxyEntry.RootOrMatchingDomain,
ProxyTarget: endpoint,
UseTLS: useTLS,
SkipTlsValidation: skipTlsValidation,
RequireBasicAuth: requireBasicAuth,
BasicAuthCredentials: targetProxyEntry.BasicAuthCredentials,
}
SaveReverseProxyConfig(&thisProxyConfigRecord)
//Update the current running config
targetProxyEntry.Domain = endpoint
targetProxyEntry.RequireTLS = useTLS
targetProxyEntry.SkipCertValidations = skipTlsValidation
targetProxyEntry.RequireBasicAuth = requireBasicAuth
dynamicProxyRouter.SaveProxy(eptype, targetProxyEntry.RootOrMatchingDomain, targetProxyEntry)
utils.SendOK(w)
} }
func DeleteProxyEndpoint(w http.ResponseWriter, r *http.Request) { func DeleteProxyEndpoint(w http.ResponseWriter, r *http.Request) {
ep, err := utils.GetPara(r, "ep") ep, err := utils.GetPara(r, "ep")
if err != nil { if err != nil {
utils.SendErrorResponse(w, "Invalid ep given") utils.SendErrorResponse(w, "Invalid ep given")
return
} }
ptype, err := utils.PostPara(r, "ptype") ptype, err := utils.PostPara(r, "ptype")
if err != nil { if err != nil {
utils.SendErrorResponse(w, "Invalid ptype given") utils.SendErrorResponse(w, "Invalid ptype given")
return
} }
err = dynamicProxyRouter.RemoveProxy(ptype, ep) err = dynamicProxyRouter.RemoveProxy(ptype, ep)
if err != nil { if err != nil {
utils.SendErrorResponse(w, err.Error()) utils.SendErrorResponse(w, err.Error())
return
} }
RemoveReverseProxyConfig(ep) RemoveReverseProxyConfig(ep)
@ -311,6 +411,139 @@ func DeleteProxyEndpoint(w http.ResponseWriter, r *http.Request) {
utils.SendOK(w) utils.SendOK(w)
} }
/*
Handle update request for basic auth credential
Require paramter: ep (Endpoint) and pytype (proxy Type)
if request with GET, the handler will return current credentials
on this endpoint by its username
if request is POST, the handler will write the results to proxy config
*/
func UpdateProxyBasicAuthCredentials(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
ep, err := utils.GetPara(r, "ep")
if err != nil {
utils.SendErrorResponse(w, "Invalid ep given")
return
}
ptype, err := utils.GetPara(r, "ptype")
if err != nil {
utils.SendErrorResponse(w, "Invalid ptype given")
return
}
//Load the target proxy object from router
targetProxy, err := dynamicProxyRouter.LoadProxy(ptype, ep)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
usernames := []string{}
for _, cred := range targetProxy.BasicAuthCredentials {
usernames = append(usernames, cred.Username)
}
js, _ := json.Marshal(usernames)
utils.SendJSONResponse(w, string(js))
} else if r.Method == http.MethodPost {
//Write to target
ep, err := utils.PostPara(r, "ep")
if err != nil {
utils.SendErrorResponse(w, "Invalid ep given")
return
}
ptype, err := utils.PostPara(r, "ptype")
if err != nil {
utils.SendErrorResponse(w, "Invalid ptype given")
return
}
if ptype != "vdir" && ptype != "subd" {
utils.SendErrorResponse(w, "Invalid ptype given")
return
}
creds, err := utils.PostPara(r, "creds")
if err != nil {
utils.SendErrorResponse(w, "Invalid ptype given")
return
}
//Load the target proxy object from router
targetProxy, err := dynamicProxyRouter.LoadProxy(ptype, ep)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
//Try to marshal the content of creds into the suitable structure
newCredentials := []*dynamicproxy.BasicAuthUnhashedCredentials{}
err = json.Unmarshal([]byte(creds), &newCredentials)
if err != nil {
utils.SendErrorResponse(w, "Malformed credential data")
return
}
//Merge the credentials into the original config
//If a new username exists in old config with no pw given, keep the old pw hash
//If a new username is found with new password, hash it and push to credential slice
mergedCredentials := []*dynamicproxy.BasicAuthCredentials{}
for _, credential := range newCredentials {
if credential.Password == "" {
//Check if exists in the old credential files
keepUnchange := false
for _, oldCredEntry := range targetProxy.BasicAuthCredentials {
if oldCredEntry.Username == credential.Username {
//Exists! Reuse the old hash
mergedCredentials = append(mergedCredentials, &dynamicproxy.BasicAuthCredentials{
Username: oldCredEntry.Username,
PasswordHash: oldCredEntry.PasswordHash,
})
keepUnchange = true
}
}
if !keepUnchange {
//This is a new username with no pw given
utils.SendErrorResponse(w, "Access password for "+credential.Username+" is empty!")
return
}
} else {
//This username have given password
mergedCredentials = append(mergedCredentials, &dynamicproxy.BasicAuthCredentials{
Username: credential.Username,
PasswordHash: auth.Hash(credential.Password),
})
}
}
targetProxy.BasicAuthCredentials = mergedCredentials
//Save it to file
thisProxyConfigRecord := Record{
ProxyType: ptype,
Rootname: targetProxy.RootOrMatchingDomain,
ProxyTarget: targetProxy.Domain,
UseTLS: targetProxy.RequireTLS,
SkipTlsValidation: targetProxy.SkipCertValidations,
RequireBasicAuth: targetProxy.RequireBasicAuth,
BasicAuthCredentials: targetProxy.BasicAuthCredentials,
}
SaveReverseProxyConfig(&thisProxyConfigRecord)
//Replace runtime configuration
dynamicProxyRouter.SaveProxy(ptype, ep, targetProxy)
utils.SendOK(w)
} else {
http.Error(w, "invalid usage", http.StatusMethodNotAllowed)
}
}
func ReverseProxyStatus(w http.ResponseWriter, r *http.Request) { func ReverseProxyStatus(w http.ResponseWriter, r *http.Request) {
js, _ := json.Marshal(dynamicProxyRouter) js, _ := json.Marshal(dynamicProxyRouter)
utils.SendJSONResponse(w, string(js)) utils.SendJSONResponse(w, string(js))

File diff suppressed because it is too large Load Diff

View File

@ -1,714 +0,0 @@
<div class="standardContainer">
<div class="ui basic segment">
<h2>Blacklist</h2>
<p>Setup blacklist based on estimated IP geographic location or IP address</p>
</div>
<div class="ui divider"></div>
<div class="ui toggle checkbox">
<input type="checkbox" id="enableBlacklist">
<label>Enable Blacklist</label>
</div>
<div id="toggleSucc" style="float: right; display:none; color: #2abd4d;" >
<i class="ui green checkmark icon"></i> Setting Saved
</div>
<div class="ui message">
<i class="info circle icon"></i> Blacklist function require complex checking logic to validate each incoming request. Not recommend enabling this feature on servers with low end hardware.
</div>
<h4>Country Blacklist</h4>
<p><i class="yellow exclamation triangle icon"></i>
This will block all requests from the selected country. The requester's location is estimated from their IP address and may not be 100% accurate.</p>
<div class="ui form">
<div class="field">
<label>Select Country</label>
<div id="countrySelector" class="ui fluid search selection dropdown">
<input type="hidden" name="country">
<i class="dropdown icon"></i>
<div class="default text">Select Country</div>
<div class="menu">
<div class="item" data-value="af"><i class="af flag"></i>Afghanistan</div>
<div class="item" data-value="ax"><i class="ax flag"></i>Aland Islands</div>
<div class="item" data-value="al"><i class="al flag"></i>Albania</div>
<div class="item" data-value="dz"><i class="dz flag"></i>Algeria</div>
<div class="item" data-value="as"><i class="as flag"></i>American Samoa</div>
<div class="item" data-value="ad"><i class="ad flag"></i>Andorra</div>
<div class="item" data-value="ao"><i class="ao flag"></i>Angola</div>
<div class="item" data-value="ai"><i class="ai flag"></i>Anguilla</div>
<div class="item" data-value="ag"><i class="ag flag"></i>Antigua</div>
<div class="item" data-value="ar"><i class="ar flag"></i>Argentina</div>
<div class="item" data-value="am"><i class="am flag"></i>Armenia</div>
<div class="item" data-value="aw"><i class="aw flag"></i>Aruba</div>
<div class="item" data-value="au"><i class="au flag"></i>Australia</div>
<div class="item" data-value="at"><i class="at flag"></i>Austria</div>
<div class="item" data-value="az"><i class="az flag"></i>Azerbaijan</div>
<div class="item" data-value="bs"><i class="bs flag"></i>Bahamas</div>
<div class="item" data-value="bh"><i class="bh flag"></i>Bahrain</div>
<div class="item" data-value="bd"><i class="bd flag"></i>Bangladesh</div>
<div class="item" data-value="bb"><i class="bb flag"></i>Barbados</div>
<div class="item" data-value="by"><i class="by flag"></i>Belarus</div>
<div class="item" data-value="be"><i class="be flag"></i>Belgium</div>
<div class="item" data-value="bz"><i class="bz flag"></i>Belize</div>
<div class="item" data-value="bj"><i class="bj flag"></i>Benin</div>
<div class="item" data-value="bm"><i class="bm flag"></i>Bermuda</div>
<div class="item" data-value="bt"><i class="bt flag"></i>Bhutan</div>
<div class="item" data-value="bo"><i class="bo flag"></i>Bolivia</div>
<div class="item" data-value="ba"><i class="ba flag"></i>Bosnia</div>
<div class="item" data-value="bw"><i class="bw flag"></i>Botswana</div>
<div class="item" data-value="bv"><i class="bv flag"></i>Bouvet Island</div>
<div class="item" data-value="br"><i class="br flag"></i>Brazil</div>
<div class="item" data-value="vg"><i class="vg flag"></i>British Virgin Islands</div>
<div class="item" data-value="bn"><i class="bn flag"></i>Brunei</div>
<div class="item" data-value="bg"><i class="bg flag"></i>Bulgaria</div>
<div class="item" data-value="bf"><i class="bf flag"></i>Burkina Faso</div>
<div class="item" data-value="mm"><i class="mm flag"></i>Burma</div>
<div class="item" data-value="bi"><i class="bi flag"></i>Burundi</div>
<div class="item" data-value="tc"><i class="tc flag"></i>Caicos Islands</div>
<div class="item" data-value="kh"><i class="kh flag"></i>Cambodia</div>
<div class="item" data-value="cm"><i class="cm flag"></i>Cameroon</div>
<div class="item" data-value="ca"><i class="ca flag"></i>Canada</div>
<div class="item" data-value="cv"><i class="cv flag"></i>Cape Verde</div>
<div class="item" data-value="ky"><i class="ky flag"></i>Cayman Islands</div>
<div class="item" data-value="cf"><i class="cf flag"></i>Central African Republic</div>
<div class="item" data-value="td"><i class="td flag"></i>Chad</div>
<div class="item" data-value="cl"><i class="cl flag"></i>Chile</div>
<div class="item" data-value="cn"><i class="cn flag"></i>China</div>
<div class="item" data-value="cx"><i class="cx flag"></i>Christmas Island</div>
<div class="item" data-value="cc"><i class="cc flag"></i>Cocos Islands</div>
<div class="item" data-value="co"><i class="co flag"></i>Colombia</div>
<div class="item" data-value="km"><i class="km flag"></i>Comoros</div>
<div class="item" data-value="cg"><i class="cg flag"></i>Congo Brazzaville</div>
<div class="item" data-value="cd"><i class="cd flag"></i>Congo</div>
<div class="item" data-value="ck"><i class="ck flag"></i>Cook Islands</div>
<div class="item" data-value="cr"><i class="cr flag"></i>Costa Rica</div>
<div class="item" data-value="ci"><i class="ci flag"></i>Cote Divoire</div>
<div class="item" data-value="hr"><i class="hr flag"></i>Croatia</div>
<div class="item" data-value="cu"><i class="cu flag"></i>Cuba</div>
<div class="item" data-value="cy"><i class="cy flag"></i>Cyprus</div>
<div class="item" data-value="cz"><i class="cz flag"></i>Czech Republic</div>
<div class="item" data-value="dk"><i class="dk flag"></i>Denmark</div>
<div class="item" data-value="dj"><i class="dj flag"></i>Djibouti</div>
<div class="item" data-value="dm"><i class="dm flag"></i>Dominica</div>
<div class="item" data-value="do"><i class="do flag"></i>Dominican Republic</div>
<div class="item" data-value="ec"><i class="ec flag"></i>Ecuador</div>
<div class="item" data-value="eg"><i class="eg flag"></i>Egypt</div>
<div class="item" data-value="sv"><i class="sv flag"></i>El Salvador</div>
<div class="item" data-value="gb"><i class="gb flag"></i>England</div>
<div class="item" data-value="gq"><i class="gq flag"></i>Equatorial Guinea</div>
<div class="item" data-value="er"><i class="er flag"></i>Eritrea</div>
<div class="item" data-value="ee"><i class="ee flag"></i>Estonia</div>
<div class="item" data-value="et"><i class="et flag"></i>Ethiopia</div>
<div class="item" data-value="eu"><i class="eu flag"></i>European Union</div>
<div class="item" data-value="fk"><i class="fk flag"></i>Falkland Islands</div>
<div class="item" data-value="fo"><i class="fo flag"></i>Faroe Islands</div>
<div class="item" data-value="fj"><i class="fj flag"></i>Fiji</div>
<div class="item" data-value="fi"><i class="fi flag"></i>Finland</div>
<div class="item" data-value="fr"><i class="fr flag"></i>France</div>
<div class="item" data-value="gf"><i class="gf flag"></i>French Guiana</div>
<div class="item" data-value="pf"><i class="pf flag"></i>French Polynesia</div>
<div class="item" data-value="tf"><i class="tf flag"></i>French Territories</div>
<div class="item" data-value="ga"><i class="ga flag"></i>Gabon</div>
<div class="item" data-value="gm"><i class="gm flag"></i>Gambia</div>
<div class="item" data-value="ge"><i class="ge flag"></i>Georgia</div>
<div class="item" data-value="de"><i class="de flag"></i>Germany</div>
<div class="item" data-value="gh"><i class="gh flag"></i>Ghana</div>
<div class="item" data-value="gi"><i class="gi flag"></i>Gibraltar</div>
<div class="item" data-value="gr"><i class="gr flag"></i>Greece</div>
<div class="item" data-value="gl"><i class="gl flag"></i>Greenland</div>
<div class="item" data-value="gd"><i class="gd flag"></i>Grenada</div>
<div class="item" data-value="gp"><i class="gp flag"></i>Guadeloupe</div>
<div class="item" data-value="gu"><i class="gu flag"></i>Guam</div>
<div class="item" data-value="gt"><i class="gt flag"></i>Guatemala</div>
<div class="item" data-value="gw"><i class="gw flag"></i>Guinea-Bissau</div>
<div class="item" data-value="gn"><i class="gn flag"></i>Guinea</div>
<div class="item" data-value="gy"><i class="gy flag"></i>Guyana</div>
<div class="item" data-value="ht"><i class="ht flag"></i>Haiti</div>
<div class="item" data-value="hm"><i class="hm flag"></i>Heard Island</div>
<div class="item" data-value="hn"><i class="hn flag"></i>Honduras</div>
<div class="item" data-value="hk"><i class="hk flag"></i>Hong Kong</div>
<div class="item" data-value="hu"><i class="hu flag"></i>Hungary</div>
<div class="item" data-value="is"><i class="is flag"></i>Iceland</div>
<div class="item" data-value="in"><i class="in flag"></i>India</div>
<div class="item" data-value="io"><i class="io flag"></i>Indian Ocean Territory</div>
<div class="item" data-value="id"><i class="id flag"></i>Indonesia</div>
<div class="item" data-value="ir"><i class="ir flag"></i>Iran</div>
<div class="item" data-value="iq"><i class="iq flag"></i>Iraq</div>
<div class="item" data-value="ie"><i class="ie flag"></i>Ireland</div>
<div class="item" data-value="il"><i class="il flag"></i>Israel</div>
<div class="item" data-value="it"><i class="it flag"></i>Italy</div>
<div class="item" data-value="jm"><i class="jm flag"></i>Jamaica</div>
<div class="item" data-value="jp"><i class="jp flag"></i>Japan</div>
<div class="item" data-value="jo"><i class="jo flag"></i>Jordan</div>
<div class="item" data-value="kz"><i class="kz flag"></i>Kazakhstan</div>
<div class="item" data-value="ke"><i class="ke flag"></i>Kenya</div>
<div class="item" data-value="ki"><i class="ki flag"></i>Kiribati</div>
<div class="item" data-value="kw"><i class="kw flag"></i>Kuwait</div>
<div class="item" data-value="kg"><i class="kg flag"></i>Kyrgyzstan</div>
<div class="item" data-value="la"><i class="la flag"></i>Laos</div>
<div class="item" data-value="lv"><i class="lv flag"></i>Latvia</div>
<div class="item" data-value="lb"><i class="lb flag"></i>Lebanon</div>
<div class="item" data-value="ls"><i class="ls flag"></i>Lesotho</div>
<div class="item" data-value="lr"><i class="lr flag"></i>Liberia</div>
<div class="item" data-value="ly"><i class="ly flag"></i>Libya</div>
<div class="item" data-value="li"><i class="li flag"></i>Liechtenstein</div>
<div class="item" data-value="lt"><i class="lt flag"></i>Lithuania</div>
<div class="item" data-value="lu"><i class="lu flag"></i>Luxembourg</div>
<div class="item" data-value="mo"><i class="mo flag"></i>Macau</div>
<div class="item" data-value="mk"><i class="mk flag"></i>Macedonia</div>
<div class="item" data-value="mg"><i class="mg flag"></i>Madagascar</div>
<div class="item" data-value="mw"><i class="mw flag"></i>Malawi</div>
<div class="item" data-value="my"><i class="my flag"></i>Malaysia</div>
<div class="item" data-value="mv"><i class="mv flag"></i>Maldives</div>
<div class="item" data-value="ml"><i class="ml flag"></i>Mali</div>
<div class="item" data-value="mt"><i class="mt flag"></i>Malta</div>
<div class="item" data-value="mh"><i class="mh flag"></i>Marshall Islands</div>
<div class="item" data-value="mq"><i class="mq flag"></i>Martinique</div>
<div class="item" data-value="mr"><i class="mr flag"></i>Mauritania</div>
<div class="item" data-value="mu"><i class="mu flag"></i>Mauritius</div>
<div class="item" data-value="yt"><i class="yt flag"></i>Mayotte</div>
<div class="item" data-value="mx"><i class="mx flag"></i>Mexico</div>
<div class="item" data-value="fm"><i class="fm flag"></i>Micronesia</div>
<div class="item" data-value="md"><i class="md flag"></i>Moldova</div>
<div class="item" data-value="mc"><i class="mc flag"></i>Monaco</div>
<div class="item" data-value="mn"><i class="mn flag"></i>Mongolia</div>
<div class="item" data-value="me"><i class="me flag"></i>Montenegro</div>
<div class="item" data-value="ms"><i class="ms flag"></i>Montserrat</div>
<div class="item" data-value="ma"><i class="ma flag"></i>Morocco</div>
<div class="item" data-value="mz"><i class="mz flag"></i>Mozambique</div>
<div class="item" data-value="na"><i class="na flag"></i>Namibia</div>
<div class="item" data-value="nr"><i class="nr flag"></i>Nauru</div>
<div class="item" data-value="np"><i class="np flag"></i>Nepal</div>
<div class="item" data-value="an"><i class="an flag"></i>Netherlands Antilles</div>
<div class="item" data-value="nl"><i class="nl flag"></i>Netherlands</div>
<div class="item" data-value="nc"><i class="nc flag"></i>New Caledonia</div>
<div class="item" data-value="pg"><i class="pg flag"></i>New Guinea</div>
<div class="item" data-value="nz"><i class="nz flag"></i>New Zealand</div>
<div class="item" data-value="ni"><i class="ni flag"></i>Nicaragua</div>
<div class="item" data-value="ne"><i class="ne flag"></i>Niger</div>
<div class="item" data-value="ng"><i class="ng flag"></i>Nigeria</div>
<div class="item" data-value="nu"><i class="nu flag"></i>Niue</div>
<div class="item" data-value="nf"><i class="nf flag"></i>Norfolk Island</div>
<div class="item" data-value="kp"><i class="kp flag"></i>North Korea</div>
<div class="item" data-value="mp"><i class="mp flag"></i>Northern Mariana Islands</div>
<div class="item" data-value="no"><i class="no flag"></i>Norway</div>
<div class="item" data-value="om"><i class="om flag"></i>Oman</div>
<div class="item" data-value="pk"><i class="pk flag"></i>Pakistan</div>
<div class="item" data-value="pw"><i class="pw flag"></i>Palau</div>
<div class="item" data-value="ps"><i class="ps flag"></i>Palestine</div>
<div class="item" data-value="pa"><i class="pa flag"></i>Panama</div>
<div class="item" data-value="py"><i class="py flag"></i>Paraguay</div>
<div class="item" data-value="pe"><i class="pe flag"></i>Peru</div>
<div class="item" data-value="ph"><i class="ph flag"></i>Philippines</div>
<div class="item" data-value="pn"><i class="pn flag"></i>Pitcairn Islands</div>
<div class="item" data-value="pl"><i class="pl flag"></i>Poland</div>
<div class="item" data-value="pt"><i class="pt flag"></i>Portugal</div>
<div class="item" data-value="pr"><i class="pr flag"></i>Puerto Rico</div>
<div class="item" data-value="qa"><i class="qa flag"></i>Qatar</div>
<div class="item" data-value="re"><i class="re flag"></i>Reunion</div>
<div class="item" data-value="ro"><i class="ro flag"></i>Romania</div>
<div class="item" data-value="ru"><i class="ru flag"></i>Russia</div>
<div class="item" data-value="rw"><i class="rw flag"></i>Rwanda</div>
<div class="item" data-value="sh"><i class="sh flag"></i>Saint Helena</div>
<div class="item" data-value="kn"><i class="kn flag"></i>Saint Kitts and Nevis</div>
<div class="item" data-value="lc"><i class="lc flag"></i>Saint Lucia</div>
<div class="item" data-value="pm"><i class="pm flag"></i>Saint Pierre</div>
<div class="item" data-value="vc"><i class="vc flag"></i>Saint Vincent</div>
<div class="item" data-value="ws"><i class="ws flag"></i>Samoa</div>
<div class="item" data-value="sm"><i class="sm flag"></i>San Marino</div>
<div class="item" data-value="gs"><i class="gs flag"></i>Sandwich Islands</div>
<div class="item" data-value="st"><i class="st flag"></i>Sao Tome</div>
<div class="item" data-value="sa"><i class="sa flag"></i>Saudi Arabia</div>
<div class="item" data-value="sn"><i class="sn flag"></i>Senegal</div>
<div class="item" data-value="cs"><i class="cs flag"></i>Serbia</div>
<div class="item" data-value="rs"><i class="rs flag"></i>Serbia</div>
<div class="item" data-value="sc"><i class="sc flag"></i>Seychelles</div>
<div class="item" data-value="sl"><i class="sl flag"></i>Sierra Leone</div>
<div class="item" data-value="sg"><i class="sg flag"></i>Singapore</div>
<div class="item" data-value="sk"><i class="sk flag"></i>Slovakia</div>
<div class="item" data-value="si"><i class="si flag"></i>Slovenia</div>
<div class="item" data-value="sb"><i class="sb flag"></i>Solomon Islands</div>
<div class="item" data-value="so"><i class="so flag"></i>Somalia</div>
<div class="item" data-value="za"><i class="za flag"></i>South Africa</div>
<div class="item" data-value="kr"><i class="kr flag"></i>South Korea</div>
<div class="item" data-value="es"><i class="es flag"></i>Spain</div>
<div class="item" data-value="lk"><i class="lk flag"></i>Sri Lanka</div>
<div class="item" data-value="sd"><i class="sd flag"></i>Sudan</div>
<div class="item" data-value="sr"><i class="sr flag"></i>Suriname</div>
<div class="item" data-value="sj"><i class="sj flag"></i>Svalbard</div>
<div class="item" data-value="sz"><i class="sz flag"></i>Swaziland</div>
<div class="item" data-value="se"><i class="se flag"></i>Sweden</div>
<div class="item" data-value="ch"><i class="ch flag"></i>Switzerland</div>
<div class="item" data-value="sy"><i class="sy flag"></i>Syria</div>
<div class="item" data-value="tw"><i class="tw flag"></i>Taiwan</div>
<div class="item" data-value="tj"><i class="tj flag"></i>Tajikistan</div>
<div class="item" data-value="tz"><i class="tz flag"></i>Tanzania</div>
<div class="item" data-value="th"><i class="th flag"></i>Thailand</div>
<div class="item" data-value="tl"><i class="tl flag"></i>Timorleste</div>
<div class="item" data-value="tg"><i class="tg flag"></i>Togo</div>
<div class="item" data-value="tk"><i class="tk flag"></i>Tokelau</div>
<div class="item" data-value="to"><i class="to flag"></i>Tonga</div>
<div class="item" data-value="tt"><i class="tt flag"></i>Trinidad</div>
<div class="item" data-value="tn"><i class="tn flag"></i>Tunisia</div>
<div class="item" data-value="tr"><i class="tr flag"></i>Turkey</div>
<div class="item" data-value="tm"><i class="tm flag"></i>Turkmenistan</div>
<div class="item" data-value="tv"><i class="tv flag"></i>Tuvalu</div>
<div class="item" data-value="ug"><i class="ug flag"></i>Uganda</div>
<div class="item" data-value="ua"><i class="ua flag"></i>Ukraine</div>
<div class="item" data-value="ae"><i class="ae flag"></i>United Arab Emirates</div>
<div class="item" data-value="us"><i class="us flag"></i>United States</div>
<div class="item" data-value="uy"><i class="uy flag"></i>Uruguay</div>
<div class="item" data-value="um"><i class="um flag"></i>Us Minor Islands</div>
<div class="item" data-value="vi"><i class="vi flag"></i>Us Virgin Islands</div>
<div class="item" data-value="uz"><i class="uz flag"></i>Uzbekistan</div>
<div class="item" data-value="vu"><i class="vu flag"></i>Vanuatu</div>
<div class="item" data-value="va"><i class="va flag"></i>Vatican City</div>
<div class="item" data-value="ve"><i class="ve flag"></i>Venezuela</div>
<div class="item" data-value="vn"><i class="vn flag"></i>Vietnam</div>
<div class="item" data-value="wf"><i class="wf flag"></i>Wallis and Futuna</div>
<div class="item" data-value="eh"><i class="eh flag"></i>Western Sahara</div>
<div class="item" data-value="ye"><i class="ye flag"></i>Yemen</div>
<div class="item" data-value="zm"><i class="zm flag"></i>Zambia</div>
<div class="item" data-value="zw"><i class="zw flag"></i>Zimbabwe</div>
</div>
</div>
</div>
<button class="ui basic red button" id="ban-btn" onclick="addCountryToBlacklist();"><i class="ui red ban icon"></i> Blacklist Country</button>
</div>
<table class="ui unstackable basic celled table">
<thead>
<tr>
<th>ISO Code</th>
<th>Remove</th>
</tr>
</thead>
<tbody id="banned-list">
</tbody>
</table>
<h4>IP Blacklist</h4>
<p>Black a certain IP or IP range</p>
<div class="ui form">
<div class="field">
<label>IP Address</label>
<input id="ipAddressInput" type="text" placeholder="IP Address">
</div>
<button id="addIpButton" onclick="addIpBlacklist();" class="ui basic red icon button">
<i class="ban icon"></i> Blacklist IP
</button>
</div>
<div class="ui message">
<i class="ui info circle icon"></i> IP Address support the following formats
<div class="ui bulleted list">
<div class="item">Fixed IP Address (e.g. 192.128.4.100)</div>
<div class="item">IP Wildcard (e.g. 172.164.*.*)</div>
<div class="item">CIDR String (e.g. 128.32.0.1/16)</div>
</div>
</div>
<table class="ui unstackable basic celled table">
<thead>
<tr>
<th>IP Address</th>
<th>Remove</th>
</tr>
</thead>
<tbody id="blacklistIpTable">
</tbody>
</table>
<div class="ui divider"></div>
<h4>Visitor IP list</h4>
<button style="margin-top: -1em;" onclick="initBlacklistQuickBanTable();" class="ui green small right floated circular basic icon button"><i class="ui refresh icon"></i></button>
<p>Observe strange traffic on your sites? Ban them in the list below.</p>
<table class="ui celled unstackable table" id="ipTable">
<thead>
<tr>
<th>IP</th>
<th>Access Count</th>
<th>Blacklist</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div class="pagination"></div>
</div>
<script>
$(".dropdown").dropdown();
function filterCountries(codesToShow) {
// get all items in the dropdown
const items = document.querySelectorAll('.ui.fluid.search.selection.dropdown .menu .item');
// 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)) {
item.style.display = 'none';
}
// otherwise, hide the item
else {
item.style.display = 'block';
}
});
}
//Get the country name from the dropdown
function getCountryName(countryCode) {
return $('#countrySelector .item[data-value="' + countryCode.toLowerCase() + '"]').text().trim();
}
function initBannedCountryList(){
$.get("/api/blacklist/list?type=country", function(data) {
let bannedListHtml = '';
data.forEach((countryCode) => {
bannedListHtml += `
<tr>
<td><i class="${countryCode} flag"></i> ${getCountryName(countryCode)} (${countryCode.toUpperCase()})</td>
<td><button class="ui red basic mini icon button" onclick="removeFromBannedList('${countryCode}')"><i class="trash icon"></i></button></td>
</tr>
`;
});
$('#banned-list').html(bannedListHtml);
filterCountries(data);
if (data.length === 0) {
$('#banned-list').append(`
<tr>
<td colspan="2">
<i class="green check circle icon"></i>
There are no blacklisted countries
</td>
</tr>
`);
}
});
}
initBannedCountryList();
function addCountryToBlacklist() {
var countryCode = $("#countrySelector").dropdown("get value").toLowerCase();
$('#countrySelector').dropdown('clear');
$.ajax({
type: "POST",
url: "/api/blacklist/country/add",
data: { cc: countryCode },
success: function(response) {
if (response.error != undefined){
alert(response.error);
}
initBannedCountryList();
},
error: function(xhr, status, error) {
// handle error response
}
});
}
function removeFromBannedList(countryCode){
if (confirm("Confirm removing " + getCountryName(countryCode) + " from blacklist?")){
countryCode = countryCode.toLowerCase();
$.ajax({
url: "/api/blacklist/country/remove",
method: "POST",
data: { cc: countryCode },
success: function(response) {
if (response.error != undefined){
alert(response.error);
}
initBannedCountryList();
},
error: function(xhr, status, error) {
console.error("Error removing country from blacklist: " + error);
// Handle error response
}
});
}
}
function initIpBanTable(){
$.get('/api/blacklist/list?type=ip', function(data) {
$('#blacklistIpTable').html("");
if (data.length === 0) {
$('#blacklistIpTable').append(`
<tr>
<td colspan="2"><i class="green check circle icon"></i>There are no blacklisted IP addresses</td>
</tr>
`);
} else {
$.each(data, function(index, ip) {
let icon = "globe icon";
if (isLAN(ip)){
icon = "desktop icon";
}else if (isHomeAddr(ip)){
icon = "home icon";
}
$('#blacklistIpTable').append(`
<tr class="blacklistItem" ip="${encodeURIComponent(ip)}">
<td><i class="${icon}"></i> ${ip}</td>
<td><button class="ui icon basic mini red button" onclick="removeIpBlacklist('${ip}');"><i class="trash alternate icon"></i></button></td>
</tr>
`);
});
}
initBlacklistQuickBanTable();
});
}
initIpBanTable();
//Check if a input is a valid IP address, wildcard of a IP address or a CIDR string
function isValidIpFilter(input) {
// Check if input is a valid IP address
const isValidIp = /^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$/.test(input);
if (isValidIp) {
return true;
}
// Check if input is a wildcard IP address
const isValidWildcardIp = /^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|\*|\*\/[0-9]|[01]?[0-9]?[0-9]-[01]?[0-9]?[0-9]|\[\d+,\d+\])\.([01]?[0-9]?[0-9]|\*|\*\/[0-9]|[01]?[0-9]?[0-9]-[01]?[0-9]?[0-9]|\[\d+,\d+\])\.([01]?[0-9]?[0-9]|\*|\*\/[0-9]|[01]?[0-9]?[0-9]-[01]?[0-9]?[0-9]|\[\d+,\d+\])$/.test(input);
if (isValidWildcardIp) {
return true;
}
// Check if input is a valid CIDR address string
const isValidCidr = /^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\/([0-9]|[1-2][0-9]|3[0-2])$/.test(input);
if (isValidCidr) {
return true;
}
// Input is not a valid IP address, wildcard IP address, or CIDR address string
return false;
}
$("#ipAddressInput").on("input", function() {
$(this).val($(this).val().trim());
var ipAddress = $(this).val();
if (!isValidIpFilter(ipAddress)) {
$(this).parent().addClass("error");
} else {
$(this).parent().removeClass("error");
}
});
function isLAN(ipAddress) {
function ip2long(ipAddress) {
// Convert the IP address to a 32-bit integer
const parts = ipAddress.split(".");
return (
(parseInt(parts[0]) << 24) |
(parseInt(parts[1]) << 16) |
(parseInt(parts[2]) << 8) |
parseInt(parts[3])
);
}
// Define the LAN IP address ranges
const LAN_RANGES = [
{ start: "10.0.0.0", end: "10.255.255.255" },
{ start: "172.16.0.0", end: "172.31.255.255" },
{ start: "192.168.0.0", end: "192.168.255.255" }
];
// Check if the IP address is within any of the LAN ranges
for (let i = 0; i < LAN_RANGES.length; i++) {
const rangeStart = ip2long(LAN_RANGES[i].start);
const rangeEnd = ip2long(LAN_RANGES[i].end);
const ipAddressLong = ip2long(ipAddress);
if (ipAddressLong >= rangeStart && ipAddressLong <= rangeEnd) {
return true;
}
}
return false;
}
function isHomeAddr(ipAddress) {
const specialIpAddresses = ['0.0.0.0', '127.0.0.1', '::1'];
return specialIpAddresses.includes(ipAddress);
}
function addIpBlacklist(){
let targetIp = $("#ipAddressInput").val().trim();
if (targetIp == ""){
alert("IP address is empty")
return
}
if (!isValidIpFilter(targetIp)){
if (!confirm("This doesn't seems like a valid IP address. Continue anyway?")){
return;
}
}
$.ajax({
url: "/api/blacklist/ip/add",
type: "POST",
data: {ip: targetIp.toLowerCase()},
success: function(response) {
if (response.error !== undefined) {
alert(response.error);
} else {
initIpBanTable();
}
$("#ipAddressInput").val("");
$("#ipAddressInput").parent().remvoeClass("error");
},
error: function() {
alert("Failed to add IP address to blacklist.");
}
});
}
function removeIpBlacklist(ipaddr){
if (confirm("Confirm remove blacklist for " + ipaddr + " ?")){
$.ajax({
url: "/api/blacklist/ip/remove",
type: "POST",
data: {ip: ipaddr.toLowerCase()},
success: function(response) {
if (response.error !== undefined) {
alert(response.error);
} else {
initIpBanTable();
}
},
error: function() {
alert("Failed to remove IP address from blacklist.");
}
});
}
}
//function to check for blacklist enable
function enableBlacklist() {
var isChecked = $('#enableBlacklist').is(':checked');
$.ajax({
type: 'POST',
url: '/api/blacklist/enable',
data: { enable: isChecked },
success: function(data){
$("#toggleSucc").stop().finish().fadeIn("fast").delay(3000).fadeOut("fast");
}
});
}
function initBlacklistEnableState(){
$.get('/api/blacklist/enable', function(data){
if (data == true){
$('#enableBlacklist').parent().checkbox("set checked");
}
//Register on change event
$("#enableBlacklist").on("change", function(){
enableBlacklist();
})
});
}
initBlacklistEnableState();
//Load the summary to ip access table
function initBlacklistQuickBanTable(){
$.get("/api/stats/summary", function(data){
initIpAccessTable(data.RequestClientIp);
})
}
initBlacklistQuickBanTable();
var blacklist_entriesPerPage = 30;
var blacklist_currentPage = 1;
var blacklist_totalPages = 0;
function initIpAccessTable(ipAccessCounts){
blacklist_totalPages = Math.ceil(Object.keys(ipAccessCounts).length / blacklist_entriesPerPage);
function sortkv(obj) {
var sortable = [];
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
sortable.push([key, obj[key]]);
}
}
sortable.sort(function(a, b) {
return b[1] - a[1];
});
var sortedObj = {};
sortable.forEach(function(item) {
sortedObj[item[0]] = item[1];
});
return sortedObj;
}
ipAccessCounts = sortkv(ipAccessCounts);
function renderTable() {
var tableBody = $("#ipTable tbody");
tableBody.empty();
var startIndex = (blacklist_currentPage - 1) * blacklist_entriesPerPage;
var endIndex = startIndex + blacklist_entriesPerPage;
var slicedEntries = Object.entries(ipAccessCounts).slice(startIndex, endIndex);
slicedEntries.forEach(function([ip, accessCount]) {
var row = $("<tr>").appendTo(tableBody);
$("<td>").text(ip).appendTo(row);
$("<td>").text(accessCount).appendTo(row);
if (ipInBlacklist(ip)){
$("<td>").html(`<button class="ui basic green small icon button" title"Unban IP" onclick="handleUnban('${ip}');"><i class="green check icon"></i></button>`).appendTo(row);
}else{
$("<td>").html(`<button class="ui basic red small icon button" title"Ban IP" onclick="handleBanIp('${ip}');"><i class="red ban icon"></i></button>`).appendTo(row);
}
});
}
function renderPagination() {
var paginationDiv = $(".pagination");
paginationDiv.empty();
for (var i = 1; i <= blacklist_totalPages; i++) {
var button = $("<button>").text(i).addClass("ui small basic compact button");
if (i === blacklist_currentPage) {
button.addClass("disabled");
}
button.click(function() {
blacklist_currentPage = parseInt($(this).text());
renderTable();
renderPagination();
});
button.appendTo(paginationDiv);
}
}
renderTable();
renderPagination();
}
function ipInBlacklist(targetIp){
let inBlacklist = false;
$(".blacklistItem").each(function(){
if ($(this).attr("ip") == encodeURIComponent(targetIp)){
inBlacklist = true;
}
});
return inBlacklist;
}
function handleBanIp(targetIp){
$("#ipAddressInput").val(targetIp);
addIpBlacklist();
}
function handleUnban(targetIp){
removeIpBlacklist(targetIp);
}
</script>

View File

@ -39,15 +39,16 @@
<input type="text" id="certdomain" placeholder="example.com / blog.example.com"> <input type="text" id="certdomain" placeholder="example.com / blog.example.com">
</div> </div>
<div class="field"> <div class="field">
<label>Public Key</label> <label>Public Key (.pem)</label>
<input type="file" id="pubkeySelector" onchange="handleFileSelect(event, 'pub')"> <input type="file" id="pubkeySelector" onchange="handleFileSelect(event, 'pub')">
</div> </div>
<div class="field"> <div class="field">
<label>Private Key</label> <label>Private Key (.key)</label>
<input type="file" id="prikeySelector" onchange="handleFileSelect(event, 'pri')"> <input type="file" id="prikeySelector" onchange="handleFileSelect(event, 'pri')">
</div> </div>
</div> </div>
<button class="ui basic button" onclick="handleDomainUploadByKeypress();"><i class="ui teal upload icon"></i> Upload</button> <button class="ui basic button" onclick="handleDomainUploadByKeypress();"><i class="ui teal upload icon"></i> Upload</button><br>
<small>You have intermediate certificate? <a style="cursor:pointer;" onclick="showSideWrapper('snippet/intermediateCertConv.html');">Open Conversion Tool</a></small>
</div> </div>
<div id="certUploadSuccMsg" class="ui green message" style="display:none;"> <div id="certUploadSuccMsg" class="ui green message" style="display:none;">
<i class="ui checkmark icon"></i> Certificate for domain <span id="certUploadingDomain"></span> uploaded. <i class="ui checkmark icon"></i> Certificate for domain <span id="certUploadingDomain"></span> uploaded.
@ -100,19 +101,25 @@
//List the stored certificates //List the stored certificates
function initManagedDomainCertificateList(){ function initManagedDomainCertificateList(){
$("#certifiedDomainList").html("");
$.get("/api/cert/list?date=true", function(data){ $.get("/api/cert/list?date=true", function(data){
if (data.error != undefined){ if (data.error != undefined){
msgbox(data.error, false, 5000); msgbox(data.error, false, 5000);
}else{ }else{
$("#certifiedDomainList").html("");
data.forEach(entry => { data.forEach(entry => {
$("#certifiedDomainList").append(`<tr> $("#certifiedDomainList").append(`<tr>
<td>${entry.Domain}</td> <td>${entry.Domain}</td>
<td>${entry.LastModifiedDate}</td> <td>${entry.LastModifiedDate}</td>
<td>${entry.ExpireDate}</td> <td>${entry.ExpireDate}</td>
<td><button title="Delete key-pair" class="ui mini basic red icon button" onclick="deleteCertificate('${entry.Domain}');"><i class="ui red trash icon"></i></button></td> <td><button title="Delete key-pair" class="ui mini basic red icon button" onclick="deleteCertificate('${entry.Domain}');"><i class="ui red trash icon"></i></button></td>
</tr>`); </tr>`);
}) });
if (data.length == 0){
$("#certifiedDomainList").append(`<tr>
<td colspan="4"><i class="ui times circle icon"></i> No valid keypairs found</td>
</tr>`);
}
} }
}) })
} }

View File

@ -126,7 +126,7 @@
var nodeCount = 0; var nodeCount = 0;
data.forEach(function(gan){ data.forEach(function(gan){
$("#GANetList").append(`<tr class="ganetEntry" addr="${gan.nwid}"> $("#GANetList").append(`<tr class="ganetEntry" addr="${gan.nwid}">
<td>${gan.nwid}</td> <td><a href="#" onclick="event.preventDefault(); openGANetDetails('${gan.nwid}');">${gan.nwid}</a></td>
<td>${gan.name}</td> <td>${gan.name}</td>
<td class="gandesc" addr="${gan.nwid}"></td> <td class="gandesc" addr="${gan.nwid}"></td>
<td class="ganetSubnet"></td> <td class="ganetSubnet"></td>

View File

@ -11,7 +11,7 @@
<a class="nettools item bluefont" data-tab="tab3">Interface</a> <a class="nettools item bluefont" data-tab="tab3">Interface</a>
</div> </div>
<div class="ui bottom attached tab segment active" data-tab="tab1"> <div class="ui bottom attached tab segment nettoolstab active" data-tab="tab1">
<h2>Multicast DNS (mDNS) Scanner</h2> <h2>Multicast DNS (mDNS) Scanner</h2>
<p>Discover mDNS enabled service in this gateway forwarded network</p> <p>Discover mDNS enabled service in this gateway forwarded network</p>
<button class="ui basic larger circular button" onclick="launchToolWithSize('./tools/mdns.html',1000, 640);">View Discovery</button> <button class="ui basic larger circular button" onclick="launchToolWithSize('./tools/mdns.html',1000, 640);">View Discovery</button>
@ -21,7 +21,7 @@
<button class="ui basic larger circular button" onclick="launchToolWithSize('./tools/ipscan.html',1000, 640);">Start Scanner</button> <button class="ui basic larger circular button" onclick="launchToolWithSize('./tools/ipscan.html',1000, 640);">Start Scanner</button>
</div> </div>
<div class="ui bottom attached tab segment" data-tab="tab2"> <div class="ui bottom attached tab segment nettoolstab" data-tab="tab2">
<div id="websshTool" style="position: relative;"> <div id="websshTool" style="position: relative;">
<h2>Web SSH</h2> <h2>Web SSH</h2>
<p>Connect to a network node within the local area network of the gateway</p> <p>Connect to a network node within the local area network of the gateway</p>
@ -101,7 +101,7 @@
<button class="ui basic button" onclick="listWoL();"><i class="ui green refresh icon"></i> Refresh</button> <button class="ui basic button" onclick="listWoL();"><i class="ui green refresh icon"></i> Refresh</button>
</div> </div>
<div class="ui bottom attached tab segment" data-tab="tab3"> <div class="ui bottom attached tab segment nettoolstab" data-tab="tab3">
<h2>Network Interfaces</h2> <h2>Network Interfaces</h2>
<p>Network Interface Card (NIC) currently installed on this host</p> <p>Network Interface Card (NIC) currently installed on this host</p>
<table id="network-interfaces-table" class="ui selectable inverted striped celled table"> <table id="network-interfaces-table" class="ui selectable inverted striped celled table">
@ -125,10 +125,10 @@
// Switch tabs when clicking on the menu items // Switch tabs when clicking on the menu items
$('.menu .nettools.item').on('click', function() { $('.menu .nettools.item').on('click', function() {
$('.menu .item').removeClass('active'); $('.menu .nettools.item').removeClass('active');
$(this).addClass('active'); $(this).addClass('active');
var tab = $(this).attr('data-tab'); var tab = $(this).attr('data-tab');
$('.tab.segment').removeClass('active'); $('.nettoolstab.tab.segment').removeClass('active');
$('div[data-tab="' + tab + '"]').addClass('active'); $('div[data-tab="' + tab + '"]').addClass('active');
}); });

View File

@ -299,4 +299,142 @@
const regex = /^(localhost|[a-z0-9]+([\-.]{1}[a-z0-9]+)*\.[a-z]{2,}|[a-z0-9]+([\-.]{1}[a-z0-9]+)*\.[a-z]{2,}\.)$/i; const regex = /^(localhost|[a-z0-9]+([\-.]{1}[a-z0-9]+)*\.[a-z]{2,}|[a-z0-9]+([\-.]{1}[a-z0-9]+)*\.[a-z]{2,}\.)$/i;
return regex.test(str); return regex.test(str);
} }
/*
Inline editor for subd.html and vdir.html
*/
function editEndpoint(endpointType, uuid) {
var row = $('tr[eptuuid="' + uuid + '"]');
var columns = row.find('td[data-label]');
var payload = $(row).attr("payload");
payload = JSON.parse(decodeURIComponent(payload));
//console.log(payload);
columns.each(function(index) {
var column = $(this);
var oldValue = column.text().trim();
if ($(this).attr("editable") == "false"){
//This col do not allow edit. Skip
return;
}
// Create an input element based on the column content
var input;
var datatype = $(this).attr("datatype");
if (datatype == "domain"){
let domain = payload.Domain;
let tls = payload.RequireTLS;
if (tls){
tls = "checked";
}else{
tls = "";
}
input = `
<div class="ui mini fluid input">
<input type="text" class="Domain" value="${domain}">
</div>
<div class="ui checkbox" style="margin-top: 0.4em;">
<input type="checkbox" class="RequireTLS" ${tls}>
<label>Require TLS</label>
</div>
`;
column.empty().append(input);
}else if (datatype == "skipver"){
let skipTLSValidation = payload.SkipCertValidations;
let checkstate = "";
if (skipTLSValidation){
checkstate = "checked";
}
column.empty().append(`<div class="ui checkbox" style="margin-top: 0.4em;">
<input type="checkbox" class="SkipCertValidations" ${checkstate}>
<label>Skip Verification</label>
<small>Check this if you are using self signed certificates</small>
</div>`);
}else if (datatype == "basicauth"){
let requireBasicAuth = payload.RequireBasicAuth;
let checkstate = "";
if (requireBasicAuth){
checkstate = "checked";
}
column.empty().append(`<div class="ui checkbox" style="margin-top: 0.4em;">
<input type="checkbox" class="RequireBasicAuth" ${checkstate}>
<label>Require Basic Auth</label>
</div> <button class="ui basic tiny button" style="margin-left: 0.4em;" onclick="editBasicAuthCredentials('${endpointType}','${uuid}');"><i class="ui blue lock icon"></i> Edit Credentials</button>`);
}else if (datatype == 'action'){
column.empty().append(`
<button title="Cancel" onclick="exitProxyInlineEdit('${endpointType}');" class="ui basic small circular icon button"><i class="ui remove icon"></i></button>
<button title="Save" onclick="saveProxyInlineEdit('${uuid}');" class="ui basic small circular icon button"><i class="ui green save icon"></i></button>
`);
}else{
//Unknown field. Leave it untouched
}
});
$("#" + endpointType).find(".editBtn").addClass("disabled");
}
function exitProxyInlineEdit(){
listSubd();
listVdirs();
$("#" + endpointType).find(".editBtn").removeClass("disabled");
}
function saveProxyInlineEdit(uuid){
var row = $('tr[eptuuid="' + uuid + '"]');
if (row.length == 0){
return;
}
var epttype = $(row).attr("class");
if (epttype == "subdEntry"){
epttype = "subd";
}else if (epttype == "vdirEntry"){
epttype = "vdir";
}
let newDomain = $(row).find(".Domain").val();
let requireTLS = $(row).find(".RequireTLS")[0].checked;
let skipCertValidations = $(row).find(".SkipCertValidations")[0].checked;
let requireBasicAuth = $(row).find(".RequireBasicAuth")[0].checked;
console.log(newDomain, requireTLS, skipCertValidations, requireBasicAuth)
$.ajax({
url: "/api/proxy/edit",
method: "POST",
data: {
"type": epttype,
"rootname": uuid,
"ep":newDomain,
"tls" :requireTLS,
"tlsval": skipCertValidations,
"bauth" :requireBasicAuth,
},
success: function(data){
if (data.error !== undefined){
msgbox(data.error, false, 6000);
}else{
msgbox("Proxy endpoint updated");
if (epttype == "subd"){
listSubd();
}else if (epttype == "vdir"){
listVdirs();
}
}
}
})
}
function editBasicAuthCredentials(endpointType, uuid){
let payload = encodeURIComponent(JSON.stringify({
ept: endpointType,
ep: uuid
}));
showSideWrapper("snippet/basicAuthEditor.html?t=" + Date.now() + "#" + payload);
}
</script> </script>

View File

@ -212,7 +212,7 @@
}else{ }else{
//Two dates are given and they are not identical //Two dates are given and they are not identical
loadStatisticByRange(startdate, enddate); loadStatisticByRange(startdate, enddate);
console.log(startdate, enddate); //console.log(startdate, enddate);
} }
} }
@ -270,7 +270,7 @@
function loadStatisticByRange(startdate, endDate){ function loadStatisticByRange(startdate, endDate){
$.getJSON("/api/analytic/loadRange?start=" + startdate + "&end=" + endDate, function(data){ $.getJSON("/api/analytic/loadRange?start=" + startdate + "&end=" + endDate, function(data){
console.log(data); //console.log(data);
//Destroy all the previous charts //Destroy all the previous charts
statisticCharts.forEach(function(thisChart){ statisticCharts.forEach(function(thisChart){
@ -368,7 +368,6 @@
function renderRefererTable(refererList){ function renderRefererTable(refererList){
const sortedEntries = Object.entries(refererList).sort(([, valueA], [, valueB]) => valueB - valueA); const sortedEntries = Object.entries(refererList).sort(([, valueA], [, valueB]) => valueB - valueA);
console.log(sortedEntries);
$("#stats_RefererTable").html(""); $("#stats_RefererTable").html("");
let endStop = 100; let endStop = 100;
if (sortedEntries.length < 100){ if (sortedEntries.length < 100){

View File

@ -547,10 +547,7 @@
//Bind event to tab switch //Bind event to tab switch
tabSwitchEventBind["status"] = function(){ tabSwitchEventBind["status"] = function(){
//On switch over to this page, resize the chart //On switch over to this page, resize the chart
$("#networkActivityPlaceHolder").hide(); handleChartAccumulateResize();
$("#networkActivity").show().delay(100, function(){
updateChartSize();
});
} }
@ -558,11 +555,6 @@
handleChartAccumulateResize(); handleChartAccumulateResize();
}); });
document.addEventListener("visibilitychange", () => {
// it could be either hidden or visible
//handleChartAccumulateResize();
});
//Initialize chart data //Initialize chart data
initChart(); initChart();

View File

@ -22,10 +22,11 @@
<button class="ui icon right floated basic button" onclick="listSubd();"><i class="green refresh icon"></i> Refresh</button> <button class="ui icon right floated basic button" onclick="listSubd();"><i class="green refresh icon"></i> Refresh</button>
<br><br> <br><br>
</div> </div>
<script> <script>
function listSubd(){ function listSubd(){
$("#subdList").html(``);
$.get("/api/proxy/list?type=subd", function(data){ $.get("/api/proxy/list?type=subd", function(data){
$("#subdList").html(``);
if (data.error !== undefined){ if (data.error !== undefined){
$("#subdList").append(`<tr> $("#subdList").append(`<tr>
<td data-label="" colspan="3"><i class="remove icon"></i> ${data.error}</td> <td data-label="" colspan="3"><i class="remove icon"></i> ${data.error}</td>
@ -37,23 +38,24 @@
}else{ }else{
data.forEach(subd => { data.forEach(subd => {
let tlsIcon = ""; let tlsIcon = "";
let subdData = encodeURIComponent(JSON.stringify(subd));
if (subd.RequireTLS){ if (subd.RequireTLS){
tlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`; tlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
} }
$("#subdList").append(`<tr> $("#subdList").append(`<tr eptuuid="${subd.RootOrMatchingDomain}" payload="${subdData}" class="subdEntry">
<td data-label="">${subd.RootOrMatchingDomain}</td> <td data-label="" editable="false">${subd.RootOrMatchingDomain}</td>
<td data-label="">${subd.Domain} ${tlsIcon}</td> <td data-label="" editable="true" datatype="domain">${subd.Domain} ${tlsIcon}</td>
<td data-label="">${!subd.SkipCertValidations?`<i class="ui green check icon"></i>`:`<i class="ui yellow exclamation circle icon" title="TLS/SSL Verification will be skipped on this host"></i>`}</td> <td data-label="" editable="true" datatype="skipver">${!subd.SkipCertValidations?`<i class="ui green check icon"></i>`:`<i class="ui yellow exclamation circle icon" title="TLS/SSL Verification will be skipped on this host"></i>`}</td>
<td data-label="">${subd.RequireBasicAuth?`<i class="ui green check icon"></i>`:`<i class="ui grey remove icon"></i>`}</td> <td data-label="" editable="true" datatype="basicauth">${subd.RequireBasicAuth?`<i class="ui green check icon"></i>`:`<i class="ui grey remove icon"></i>`}</td>
<td class="center aligned" data-label=""> <td class="center aligned" editable="true" datatype="action" data-label="">
<button class="ui circular mini basic icon button" onclick='editEndpoint("subd","${subd.RootOrMatchingDomain}")'><i class="edit icon"></i></button> <button class="ui circular mini basic icon button editBtn" onclick='editEndpoint("subd","${subd.RootOrMatchingDomain}")'><i class="edit icon"></i></button>
<button class="ui circular mini red basic icon button" onclick='deleteEndpoint("subd","${subd.RootOrMatchingDomain}")'><i class="trash icon"></i></button> <button class="ui circular mini red basic icon button" onclick='deleteEndpoint("subd","${subd.RootOrMatchingDomain}")'><i class="trash icon"></i></button>
</td> </td>
</tr>`); </tr>`);
}); });
} }
}); });
} }
//Bind on tab switch events //Bind on tab switch events
tabSwitchEventBind["subd"] = function(){ tabSwitchEventBind["subd"] = function(){

View File

@ -41,81 +41,21 @@
</div> </div>
<button id="addTcpProxyButton" class="ui basic button" type="submit"><i class="ui blue add icon"></i> Create</button> <button id="addTcpProxyButton" class="ui basic button" type="submit"><i class="ui blue add icon"></i> Create</button>
<button id="editTcpProxyButton" class="ui basic button" onclick="confirmEditTCPProxyConfig(event);"><i class="ui blue save icon"></i> Update</button> <button id="editTcpProxyButton" class="ui basic button" onclick="confirmEditTCPProxyConfig(event);"><i class="ui blue save icon"></i> Update</button>
<table class="ui celled padded table"> <button class="ui basic red button" onclick="event.preventDefault(); cancelTCPProxyEdit(event);"><i class="ui red remove icon"></i> Cancel</button>
<thead>
<tr><th class="single line">Mode</th>
<th>Public-IP</th>
<th>Concurrent Access</th>
<th>Flow Diagram</th>
</tr></thead>
<tbody>
<tr>
<td>
<h3 class="ui center aligned header">Listen</h3>
</td>
<td class="single line">
Server: <i class="ui green check icon"></i><br>
A: <i class="ui remove icon"></i><br>
B: <i class="ui remove icon"></i><br>
</td>
<td>
<i class="ui red times icon"></i>
</td>
<td>Port A (e.g. 8080) <i class="arrow right icon"></i> Server<br>
Port B (e.g. 8081) <i class="arrow right icon"></i> Server<br>
<small>Server will act as a bridge to proxy traffic between Port A and B</small>
</td>
</tr>
<tr>
<td>
<h3 class="ui center aligned header">Transport</h3>
</td>
<td class="single line">
Server: <i class="ui green check icon"></i><br>
A: <i class="ui remove icon"></i><br>
B: <i class="ui green check icon"></i> (or same LAN)<br>
</td>
<td>
<i class="ui green check icon"></i>
</td>
<td>Port A (e.g. 25565) <i class="arrow right icon"></i> Server<br>
Server <i class="arrow right icon"></i> Port B (e.g. 192.168.0.2:25565)<br>
<small>Traffic from Port A will be forward to Port B's (IP if provided and) Port</small>
</td>
</tr>
<tr>
<td>
<h3 class="ui center aligned header">Starter</h3>
</td>
<td class="single line">
Server: <i class="ui times icon"></i><br>
A: <i class="ui green check icon"></i><br>
B: <i class="ui green check icon"></i><br>
</td>
<td>
<i class="ui red times icon"></i>
</td>
<td>Server <i class="arrow right icon"></i> Port A (e.g. remote.local.:8080) <br>
Server <i class="arrow right icon"></i> Port B (e.g. recv.local.:8081) <br>
<small>Port A and B will be actively bridged</small>
</td>
</tr>
</tbody>
</table>
</form> </form>
<div class="ui divider"></div> <div class="ui divider"></div>
</div> </div>
<div class="ui basic segment"> <div class="ui basic segment">
<div style="overflow-x: auto;"> <h3>TCP Proxy Configs</h3>
<h3>TCP Proxy Configs</h3> <p>A list of TCP proxy configs created on this host. To enable them, use the toggle button on the right.</p>
<p>A list of TCP proxy configs created on this host. To enable them, use the toggle button on the right.</p> <div style="overflow-x: auto; min-height: 400px;">
<table id="proxyTable" class="ui basic celled unstackable table"> <table id="proxyTable" class="ui celled unstackable table">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>PortA</th> <th>Port/Addr A</th>
<th>PortB</th> <th>Port/Addr B</th>
<th>Mode</th> <th>Mode</th>
<th>Timeout (s)</th> <th>Timeout (s)</th>
<th>Actions</th> <th>Actions</th>
@ -127,6 +67,72 @@
</table> </table>
</div> </div>
</div> </div>
<div class="ui basic inverted segment" style="background-color: #414141; border-radius: 0.6em;">
<h3>Proxy Mode</h3>
<p>TCP Proxy support the following TCP sockets proxy modes</p>
<table class="ui celled padded inverted basic table">
<thead>
<tr><th class="single line">Mode</th>
<th>Public-IP</th>
<th>Concurrent Access</th>
<th>Flow Diagram</th>
</tr></thead>
<tbody>
<tr>
<td>
<h4 class="ui center aligned inverted header">Transport</h4>
</td>
<td class="single line">
Server: <i class="ui green check icon"></i><br>
A: <i class="ui remove icon"></i><br>
B: <i class="ui green check icon"></i> (or same LAN)<br>
</td>
<td>
<i class="ui green check icon"></i>
</td>
<td>Port A (e.g. 25565) <i class="arrow right icon"></i> Server<br>
Server <i class="arrow right icon"></i> Port B (e.g. 192.168.0.2:25565)<br>
<small>Traffic from Port A will be forward to Port B's (IP if provided and) Port</small>
</td>
</tr>
<tr>
<td>
<h4 class="ui center aligned inverted header">Listen</h4>
</td>
<td class="single line">
Server: <i class="ui green check icon"></i><br>
A: <i class="ui remove icon"></i><br>
B: <i class="ui remove icon"></i><br>
</td>
<td>
<i class="ui red times icon"></i>
</td>
<td>Port A (e.g. 8080) <i class="arrow right icon"></i> Server<br>
Port B (e.g. 8081) <i class="arrow right icon"></i> Server<br>
<small>Server will act as a bridge to proxy traffic between Port A and B</small>
</td>
</tr>
<tr>
<td>
<h4 class="ui center aligned inverted header">Starter</h4>
</td>
<td class="single line">
Server: <i class="ui times icon"></i><br>
A: <i class="ui green check icon"></i><br>
B: <i class="ui green check icon"></i><br>
</td>
<td>
<i class="ui red times icon"></i>
</td>
<td>Server <i class="arrow right icon"></i> Port A (e.g. remote.local.:8080) <br>
Server <i class="arrow right icon"></i> Port B (e.g. recv.local.:8081) <br>
<small>Port A and B will be actively bridged</small>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<script> <script>
let editingTCPProxyConfigUUID = ""; //The current editing TCP Proxy config UUID let editingTCPProxyConfigUUID = ""; //The current editing TCP Proxy config UUID
@ -171,11 +177,30 @@
function clearTCPProxyAddEditForm(){ function clearTCPProxyAddEditForm(){
$('#tcpProxyForm input, #tcpProxyForm select').val(''); $('#tcpProxyForm input, #tcpProxyForm select').val('');
$('#tcpProxyForm select').dropdown('clear');
}
function cancelTCPProxyEdit(event) {
clearTCPProxyAddEditForm();
$('#addproxyConfig').slideUp('fast');
} }
function validateTCPProxyConfig(form){ function validateTCPProxyConfig(form){
//Check if name is filled. If not, generate a random name for it
var name = form.find('input[name="name"]').val()
if (name == ""){
let randomName = "Proxy Rule (#" + Math.round(Date.now()/1000) + ")";
form.find('input[name="name"]').val(randomName);
}
// Validate timeout is an integer // Validate timeout is an integer
var timeout = parseInt(form.find('input[name="timeout"]').val()); var timeout = parseInt(form.find('input[name="timeout"]').val());
if (form.find('input[name="timeout"]').val() == ""){
//Not set. Assign a random one to it
form.find('input[name="timeout"]').val("10");
timeout = 10;
}
if (isNaN(timeout)) { if (isNaN(timeout)) {
form.find('input[name="timeout"]').parent().addClass("error"); form.find('input[name="timeout"]').parent().addClass("error");
msgbox('Timeout must be a valid integer', false, 5000); msgbox('Timeout must be a valid integer', false, 5000);
@ -206,10 +231,10 @@
proxyConfigs.forEach(function(config) { proxyConfigs.forEach(function(config) {
var runningLogo = '<i class="red circle icon"></i>'; var runningLogo = '<i class="red circle icon"></i>';
var startButton = `<button onclick="startTcpProx('${config.UUID}');" class="ui button" title="Start Proxy"><i class="play icon"></i></button>`; var startButton = `<button onclick="startTcpProx('${config.UUID}');" class="ui button" title="Start Proxy"><i class="play icon"></i> Start Proxy</button>`;
if (config.Running){ if (config.Running){
runningLogo = '<i class="green circle icon"></i>'; runningLogo = '<i class="green circle icon"></i>';
startButton = `<button onclick="stopTcpProx('${config.UUID}');" class="ui button" title="Start Proxy"><i class="red stop icon"></i></button>`; startButton = `<button onclick="stopTcpProx('${config.UUID}');" class="ui button" title="Start Proxy"><i class="red stop icon"></i> Stop Proxy</button>`;
} }
var modeText = "Unknown"; var modeText = "Unknown";
@ -230,11 +255,11 @@
row.append($('<td>').text(modeText)); row.append($('<td>').text(modeText));
row.append($('<td>').text(config.Timeout)); row.append($('<td>').text(config.Timeout));
row.append($('<td>').html(` row.append($('<td>').html(`
<div class="ui basic icon buttons"> <div class="ui basic vertical fluid tiny buttons">
<button class="ui button" onclick="validateProxyConfig('${config.UUID}', this);" title="Validate Config"><i class="teal question circle outline icon"></i></button> <button class="ui button" onclick="validateProxyConfig('${config.UUID}', this);" title="Validate Config"><i class="teal question circle outline icon"></i> CXN Test</button>
${startButton} ${startButton}
<button onclick="editTCPProxyConfig('${config.UUID}');" class="ui button" title="Edit Config"><i class="edit icon"></i></button> <button onclick="editTCPProxyConfig('${config.UUID}');" class="ui button" title="Edit Config"><i class="edit icon"></i> Edit </button>
<button onclick="deleteTCPProxyConfig('${config.UUID}');" class="ui red button" title="Delete Config"><i class="trash icon"></i></button> <button onclick="deleteTCPProxyConfig('${config.UUID}');" class="ui red basic button" title="Delete Config"><i class="trash icon"></i> Remove</button>
</div> </div>
`)); `));
tableBody.append(row); tableBody.append(row);
@ -261,10 +286,11 @@
data: {uuid: configUUID}, data: {uuid: configUUID},
success: function(data){ success: function(data){
if (data.error != undefined){ if (data.error != undefined){
$(btn).html(`<i class="red times icon"></i>`); let errormsg = data.error.charAt(0).toUpperCase() + data.error.slice(1);
$(btn).html(`<i class="red times icon"></i> ${errormsg}`);
msgbox(data.error, false, 6000); msgbox(data.error, false, 6000);
}else{ }else{
$(btn).html(`<i class="green check icon"></i>`); $(btn).html(`<i class="green check icon"></i> Config Valid`);
msgbox("Config Check Passed"); msgbox("Config Check Passed");
} }
} }
@ -335,7 +361,55 @@
} }
function deleteTCPProxyConfig(configUUID){ function deleteTCPProxyConfig(configUUID){
$.ajax({
url: "/api/tcpprox/config/delete",
method: "POST",
data: {uuid: configUUID},
success: function(data){
if (data.error != undefined){
msgbox(data.error, false, 6000);
}else{
msgbox("Proxy Config Removed");
initProxyConfigList();
}
}
})
}
//Start a TCP proxy by their config UUID
function startTcpProx(configUUID){
$.ajax({
url: "/api/tcpprox/config/start",
method: "POST",
data: {uuid: configUUID},
success: function(data){
if (data.error != undefined){
msgbox(data.error, false, 6000);
}else{
msgbox("Service Started");
initProxyConfigList();
}
}
});
}
//Stop a TCP proxy by their config UUID
function stopTcpProx(configUUID){
$.ajax({
url: "/api/tcpprox/config/stop",
method: "POST",
data: {uuid: configUUID},
success: function(data){
if (data.error != undefined){
msgbox(data.error, false, 6000);
}else{
msgbox("Service Stopped");
initProxyConfigList();
}
}
});
} }
function initProxyConfigList(){ function initProxyConfigList(){

View File

@ -4,6 +4,8 @@
<p>You might find these tools or information helpful when setting up your gateway server</p> <p>You might find these tools or information helpful when setting up your gateway server</p>
</div> </div>
<div class="ui divider"></div> <div class="ui divider"></div>
<div class="selfauthOnly"> <div class="selfauthOnly">
<h3>Account Management</h3> <h3>Account Management</h3>
<p>Functions to help management the current account</p> <p>Functions to help management the current account</p>
@ -34,6 +36,11 @@
<p>The following SMTP settings help you to reset your password in case you have lost your management account.</p> <p>The following SMTP settings help you to reset your password in case you have lost your management account.</p>
<form id="email-form" class="ui form"> <form id="email-form" class="ui form">
<div class="field"> <div class="field">
<label>Sender Address</label>
<input type="text" name="senderAddr" placeholder="E.g. noreply@zoraxy.arozos.com">
</div>
<div class="field">
<p><i class="caret down icon"></i> Connection setup for email service provider</p>
<div class="fields"> <div class="fields">
<div class="twelve wide field"> <div class="twelve wide field">
<label>SMTP Provider Hostname</label> <label>SMTP Provider Hostname</label>
@ -46,36 +53,35 @@
</div> </div>
</div> </div>
</div> </div>
<div class="field">
<label>Sender Domain</label>
<input type="text" name="domain" min="1" max="65534" placeholder="E.g. arozos.com">
</div>
<div class="field">
<label>Sender Address</label>
<input type="text" name="senderAddr" placeholder="E.g. admin@zoraxy.arozos.com">
</div>
<div class="field"> <div class="field">
<p><i class="caret down icon"></i> Credentials for SMTP server authentications</p>
<div class="two fields"> <div class="two fields">
<div class="field"> <div class="field">
<label>Sender Username</label> <label>Sender Username</label>
<input type="text" name="username" placeholder="Username of the email account"> <input type="text" name="username" placeholder="E.g. admin">
</div> </div>
<div class="field"> <div class="field">
<label>Sender Password</label> <label>Sender Domain</label>
<input type="password" name="password" placeholder="Password of the email account"> <div class="ui labeled input">
<small>Leave empty to use the old password</small> <div class="ui basic label">
@
</div>
<input type="text" name="domain" min="1" max="65534" placeholder="E.g. arozos.com">
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="ui divider"></div>
<p> Email for sending account reset link</p>
<div class="field"> <div class="field">
<label>Admin Address</label> <label>Sender Password</label>
<input type="password" name="password" placeholder="Password of the email account">
<small>Leave empty to use the old password</small>
</div>
<p> <i class="caret down icon"></i> Email for sending account reset link</p>
<div class="field">
<label>Admin / Receiver Address</label>
<input type="text" name="recvAddr" placeholder="E.g. personalEmail@gmail.com"> <input type="text" name="recvAddr" placeholder="E.g. personalEmail@gmail.com">
</div> </div>
@ -83,7 +89,7 @@
<button class="ui basic button" onclick="event.preventDefault(); sendTestEmail(this);"><i class="teal mail icon"></i> Send Test Email</button> <button class="ui basic button" onclick="event.preventDefault(); sendTestEmail(this);"><i class="teal mail icon"></i> Send Test Email</button>
</form> </form>
</div> </div>
<div class="ui divider"></div>
<h3> IP Address to CIDR</h3> <h3> IP Address to CIDR</h3>
<p>No experience with CIDR notations? Here are some tools you can use to make setting up easier.</p> <p>No experience with CIDR notations? Here are some tools you can use to make setting up easier.</p>
<div class="ui basic segment"> <div class="ui basic segment">
@ -110,6 +116,45 @@
</div> </div>
<p>Results: <div id="ipRangeOutput">N/A</div></p> <p>Results: <div id="ipRangeOutput">N/A</div></p>
</div> </div>
<div class="ui divider"></div>
<div id="zoraxyinfo">
<h3 class="ui header">
System Information
</h3>
<p>Basic information about this zoraxy host</p>
<table class="ui very basic collapsing celled table">
<tbody>
<tr>
<td>Host UUID</td>
<td class="uuid"></td>
</tr>
<tr>
<td>Version</td>
<td class="version"></td>
</tr>
<tr>
<td>Build</td>
<td class="development"></td>
</tr>
<tr>
<td>Running Since</td>
<td class="boottime"></td>
</tr>
<tr>
<td>ZeroTier Linked</td>
<td class="zt"></td>
</tr>
<tr>
<td>Enable SSH Loopback</td>
<td class="sshlb"></td>
</tr>
</tbody>
</table>
<p>Zoraxy is developed by tobychui for <a href="//imuslab.com" target="_blank">imuslab</a> and open source under <a href="https://www.gnu.org/licenses/agpl-3.0.txt">AGPL</a></p>
</div>
<br>
</div> </div>
<script> <script>
@ -122,7 +167,44 @@
//Using external auth manager. Hide options //Using external auth manager. Hide options
$(".selfauthOnly").hide(); $(".selfauthOnly").hide();
} }
}) });
$.get("/api/info/x", function(data){
function timeConverter(UNIX_timestamp){
var a = new Date(UNIX_timestamp * 1000);
var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
var year = a.getFullYear();
var month = months[a.getMonth()];
var date = a.getDate();
var hour = a.getHours();
var min = a.getMinutes();
var sec = a.getSeconds();
var time = date + ' ' + month + ' ' + year + ' ' + hour + ':' + min + ':' + sec ;
return time;
}
function secondsToDhms(seconds) {
seconds = Number(seconds);
var d = Math.floor(seconds / (3600*24));
var h = Math.floor(seconds % (3600*24) / 3600);
var m = Math.floor(seconds % 3600 / 60);
var s = Math.floor(seconds % 60);
var dDisplay = d > 0 ? d + (d == 1 ? " day, " : " days, ") : "";
var hDisplay = h > 0 ? h + (h == 1 ? " hour, " : " hours, ") : "";
var mDisplay = m > 0 ? m + (m == 1 ? " minute, " : " minutes, ") : "";
var sDisplay = s > 0 ? s + (s == 1 ? " second" : " seconds") : "";
return dDisplay + hDisplay + mDisplay + sDisplay;
}
console.log(data);
$("#zoraxyinfo .uuid").text(data.NodeUUID);
$("#zoraxyinfo .development").text(data.Development?"Development":"Release");
$("#zoraxyinfo .version").text(data.Version);
$("#zoraxyinfo .boottime").text(timeConverter(data.BootTime) + ` ( ${secondsToDhms(parseInt(Date.now()/1000) - data.BootTime)} ago)`);
$("#zoraxyinfo .zt").html(data.ZerotierConnected?`<i class="ui green check icon"></i> Connected`:`<i class="ui red times icon"></i> Link Error`);
$("#zoraxyinfo .sshlb").html(data.EnableSshLoopback?`<i class="ui yellow exclamation triangle icon"></i> Enabled`:`Disabled`);
});
function changePassword() { function changePassword() {
const oldPassword = document.getElementsByName('oldPassword')[0].value; const oldPassword = document.getElementsByName('oldPassword')[0].value;

View File

@ -27,8 +27,8 @@
<script> <script>
//Virtual directories functions //Virtual directories functions
function listVdirs(){ function listVdirs(){
$("#vdirList").html(``);
$.get("/api/proxy/list?type=vdir", function(data){ $.get("/api/proxy/list?type=vdir", function(data){
$("#vdirList").html(``);
if (data.error !== undefined){ if (data.error !== undefined){
$("#vdirList").append(`<tr> $("#vdirList").append(`<tr>
<td data-label="" colspan="3"><i class="remove icon"></i> ${data.error}</td> <td data-label="" colspan="3"><i class="remove icon"></i> ${data.error}</td>
@ -40,16 +40,17 @@
}else{ }else{
data.forEach(vdir => { data.forEach(vdir => {
let tlsIcon = ""; let tlsIcon = "";
let vdirData = encodeURIComponent(JSON.stringify(vdir));
if (vdir.RequireTLS){ if (vdir.RequireTLS){
tlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`; tlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
} }
$("#vdirList").append(`<tr> $("#vdirList").append(`<tr eptuuid="${vdir.RootOrMatchingDomain}" payload="${vdirData}" class="vdirEntry">
<td data-label="">${vdir.RootOrMatchingDomain}</td> <td data-label="" editable="false">${vdir.RootOrMatchingDomain}</td>
<td data-label="">${vdir.Domain} ${tlsIcon}</td> <td data-label="" editable="true" datatype="domain">${vdir.Domain} ${tlsIcon}</td>
<td data-label="">${!subd.SkipCertValidations?`<i class="ui green check icon"></i>`:`<i class="ui yellow exclamation circle icon" title="TLS/SSL Verification will be skipped on this host"></i>`}</td> <td data-label="" editable="true" datatype="skipver">${!vdir.SkipCertValidations?`<i class="ui green check icon"></i>`:`<i class="ui yellow exclamation circle icon" title="TLS/SSL Verification will be skipped on this host"></i>`}</td>
<td data-label="">${subd.RequireBasicAuth?`<i class="ui green check icon"></i>`:`<i class="ui grey remove icon"></i>`}</td> <td data-label="" editable="true" datatype="basicauth">${vdir.RequireBasicAuth?`<i class="ui green check icon"></i>`:`<i class="ui grey remove icon"></i>`}</td>
<td class="center aligned" data-label=""> <td class="center aligned" editable="true" datatype="action" data-label="">
<button class="ui circular mini basic icon button" onclick='editEndpoint("vdir","${vdir.RootOrMatchingDomain}")'><i class="edit icon"></i></button> <button class="ui circular mini basic icon button editBtn" onclick='editEndpoint("vdir","${vdir.RootOrMatchingDomain}")'><i class="edit icon"></i></button>
<button class="ui circular mini red basic icon button" onclick='deleteEndpoint("vdir","${vdir.RootOrMatchingDomain}")'><i class="trash icon"></i></button> <button class="ui circular mini red basic icon button" onclick='deleteEndpoint("vdir","${vdir.RootOrMatchingDomain}")'><i class="trash icon"></i></button>
</td> </td>
</tr>`); </tr>`);

View File

@ -1,11 +1,11 @@
<html> <html>
<head> <head>
<!-- Zoraxy Forbidden Template -->
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.5.0/semantic.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.5.0/semantic.min.css">
<script type="text/javascript" src="https://code.jquery.com/jquery-3.6.4.min.js"></script> <script type="text/javascript" src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.5.0/semantic.min.js <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.5.0/semantic.min.js"></script>
"></script>
<title>Forbidden</title> <title>Forbidden</title>
<style> <style>
#msg{ #msg{
@ -39,7 +39,7 @@
<h3 style="margin-top: 1em;">403 - Forbidden</h3> <h3 style="margin-top: 1em;">403 - Forbidden</h3>
<div class="ui divider"></div> <div class="ui divider"></div>
<p>You do not have permission to view this directory or page. <br> <p>You do not have permission to view this directory or page. <br>
This might be caused by the site admin has blacklisted your country or IP address</p> This might cause by the region limit setting of this site.</p>
<div class="ui divider"></div> <div class="ui divider"></div>
<div style="text-align: left;"> <div style="text-align: left;">
<small>Request time: <span id="reqtime"></span></small><br> <small>Request time: <span id="reqtime"></span></small><br>

View File

@ -55,8 +55,8 @@
<a class="item" tag="redirectset"> <a class="item" tag="redirectset">
<i class="simplistic level up alternate icon"></i> Redirection <i class="simplistic level up alternate icon"></i> Redirection
</a> </a>
<a class="item" tag="blacklist"> <a class="item" tag="access">
<i class="simplistic ban icon"></i> Blacklist <i class="simplistic ban icon"></i> Access Control
</a> </a>
<div class="ui divider menudivider">Bridging</div> <div class="ui divider menudivider">Bridging</div>
<a class="item" tag="gan"> <a class="item" tag="gan">
@ -109,7 +109,7 @@
<div id="redirectset" class="functiontab" target="redirection.html"></div> <div id="redirectset" class="functiontab" target="redirection.html"></div>
<!-- Blacklist --> <!-- Blacklist -->
<div id="blacklist" class="functiontab" target="blacklist.html"></div> <div id="access" class="functiontab" target="access.html"></div>
<!-- Global Area Networking --> <!-- Global Area Networking -->
<div id="gan" class="functiontab" target="gan.html"></div> <div id="gan" class="functiontab" target="gan.html"></div>
@ -131,6 +131,13 @@
</div> </div>
</div> </div>
</div> </div>
<div class="sideWrapper" style="display:none;">
<div class="fadingBackground" onclick="hideSideWrapper();"></div>
<div class="content">
<div class="sideWrapperMenu"></div>
<iframe src="snippet/placeholder.html"></iframe>
</div>
</div>
<br><br> <br><br>
<div class="ui divider"></div> <div class="ui divider"></div>
<div class="ui container" style="color: grey; font-size: 90%"> <div class="ui container" style="color: grey; font-size: 90%">
@ -251,7 +258,6 @@
}); });
$('html,body').animate({scrollTop: 0}, 'fast'); $('html,body').animate({scrollTop: 0}, 'fast');
window.location.hash = tabID; window.location.hash = tabID;
} }
$(window).on("resize", function(){ $(window).on("resize", function(){
@ -274,6 +280,36 @@
$("#messageBox").stop().finish().fadeIn("fast").delay(delayDuration).fadeOut("fast"); $("#messageBox").stop().finish().fadeIn("fast").delay(delayDuration).fadeOut("fast");
} }
/*
Toggles for side wrapper
*/
function showSideWrapper(scriptpath=""){
if (scriptpath != ""){
$(".sideWrapper iframe").attr("src", scriptpath);
}
if ($(".sideWrapper .content").transition("is animating") || $(".sideWrapper .content").transition("is visible")){
return
}
$(".sideWrapper").show();
$(".sideWrapper .fadingBackground").fadeIn("fast");
$(".sideWrapper .content").transition('slide left in', 300);
}
function hideSideWrapper(discardFrameContent = false){
if (discardFrameContent){
$(".sideWrapper iframe").attr("src", "snippet/placeholder.html");
}
if ($(".sideWrapper .content").transition("is animating") || !$(".sideWrapper .content").transition("is visible")){
return
}
$(".sideWrapper .content").transition('slide left out', 300, function(){
$(".sideWrapper .fadingBackground").fadeOut("fast", function(){
$(".sideWrapper").hide();
});
});
}
</script> </script>
</body> </body>
</html> </html>

View File

@ -86,6 +86,7 @@ body{
right: 1em; right: 1em;
display:none; display:none;
max-width: 300px; max-width: 300px;
z-index: 999;
} }
/* Standard containers */ /* Standard containers */
@ -119,6 +120,45 @@ body{
padding-bottom: 0; padding-bottom: 0;
} }
/*
Side Wrapper
*/
.sideWrapper{
position: fixed;
right: 0;
top: 52px;
height: 100%;
width: calc(100% - 5em);
max-width: 500px;
z-index: 10;
}
.sideWrapper .content{
height: 100%;
width: 100%;
position: relative;
background-color: white;
}
.sideWrapper iframe{
height: 100%;
width: 100%;
border: 0px solid transparent;
}
.sideWrapper .fadingBackground{
position: fixed;
top: 52px;
left: 0;
width: 100%;
height: calc(100% - 52px);
background-color: rgba(38,38,38,0.26);
}
.sideWrapperMenu{
height: 3px;
background-color: #414141;
}
/* /*
RWD Rules RWD Rules
@ -398,6 +438,27 @@ body{
} }
} }
/*
Access Control
*/
.ui.very.compact.table th{
padding-top: 0.2em;
padding-bottom: 0.2em;
}
.ui.very.compact.table td{
padding-top: 0.1em;
padding-bottom: 0.1em;
}
#ipTable.disabled{
opacity: 0.5;
pointer-events: none;
user-select: none;
}
/* /*
Uptime Monitor Uptime Monitor
*/ */

View File

@ -0,0 +1,196 @@
<!DOCTYPE html>
<html>
<head>
<!-- Notes: This should be open in its original path-->
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
<script src="../script/jquery-3.6.0.min.js"></script>
<script src="../script/semantic/semantic.min.js"></script>
</head>
<body>
<br>
<div class="ui container">
<div class="ui header">
<div class="content">
Basic Auth Credential
<div class="sub header" id="epname"></div>
</div>
</div>
<div class="scrolling content ui form">
<div id="inlineEditBasicAuthCredentials" class="field">
<p>Enter the username and password for allowing them to access this proxy endpoint</p>
<table class="ui very basic compacted unstackable celled table">
<thead>
<tr>
<th>Username</th>
<th>Password</th>
<th>Remove</th>
</tr></thead>
<tbody id="inlineEditBasicAuthCredentialTable">
<tr>
<td colspan="3"><i class="ui green circle check icon"></i> No Entered Credential</td>
</tr>
</tbody>
</table>
<div class="three small fields credentialEntry">
<div class="field">
<input id="inlineEditBasicAuthCredUsername" type="text" placeholder="Username" autocomplete="off">
</div>
<div class="field">
<input id="inlineEditBasicAuthCredPassword" type="password" placeholder="Password" autocomplete="off">
</div>
<div class="field" >
<button class="ui basic button" onclick="addCredentialsToEditingList();"><i class="blue add icon"></i> Add Credential</button>
</div>
<div class="ui divider"></div>
<div class="field" >
<button class="ui basic button" style="float: right;" onclick="saveCredentials();"><i class="green save icon"></i> Save</button>
<button class="ui basic button" style="float: right;" onclick="cancelCredentialEdit();"><i class="remove icon"></i> Cancel</button>
</div>
</div>
</div>
</div>
</div>
<script>
let editingCredentials = [];
let editingEndpoint = {};
if (window.location.hash.length > 1){
let payloadHash = window.location.hash.substr(1);
try{
payloadHash = JSON.parse(decodeURIComponent(payloadHash));
loadBasicAuthCredentials(payloadHash.ept, payloadHash.ep);
$("#epname").text(payloadHash.ep);
editingEndpoint = payloadHash;
}catch(ex){
console.log("Unable to load endpoint data from hash")
}
}
function loadBasicAuthCredentials(endpointType, uuid){
$.ajax({
url: "/api/proxy/updateCredentials",
method: "GET",
data: {
ep: uuid,
ptype: endpointType
},
success: function(data){
//Push the existing account to list
for(var i = 0; i < data.length; i++){
// Create a new credential object
var credential = {
username: data[i],
password: ""
};
// Add the credential to the global credentials array
editingCredentials.push(credential);
}
console.log(data);
updateEditingCredentialList();
}
})
}
function addCredentialsToEditingList() {
// Retrieve the username and password input values
var username = $('#inlineEditBasicAuthCredUsername').val();
var password = $('#inlineEditBasicAuthCredPassword').val();
if(username == "" || password == ""){
parent.msgbox("Username or password cannot be empty", false, 5000);
return;
}
if (alreadyExists(username)){
parent.msgbox("Credential with same username already exists", false, 5000);
return;
}
// Create a new credential object
var credential = {
username: username,
password: password
};
// Add the credential to the global credentials array
editingCredentials.push(credential);
// Clear the input fields
$('#inlineEditBasicAuthCredUsername').val('');
$('#inlineEditBasicAuthCredPassword').val('');
// Update the table body with the credentials
updateEditingCredentialList();
}
function updateEditingCredentialList() {
var tableBody = $('#inlineEditBasicAuthCredentialTable');
tableBody.empty();
if (editingCredentials.length === 0) {
tableBody.append('<tr><td colspan="3"><i class="ui green circle check icon"></i> No Entered Credential</td></tr>');
} else {
for (var i = 0; i < editingCredentials.length; i++) {
var credential = editingCredentials[i];
var username = credential.username;
var password = credential.password.replace(/./g, '*'); // Replace each character with '*'
if (credential.password == ""){
password = `<span style="color: #c9c9c9;"><i class="eye slash outline icon"></i> Hidden<span>`;
}
var row = '<tr>' +
'<td>' + username + '</td>' +
'<td>' + password + '</td>' +
'<td><button class="ui basic button" onclick="removeCredentialFromEditingList(' + i + ');"><i class="red remove icon"></i> Remove</button></td>' +
'</tr>';
tableBody.append(row);
}
}
}
function removeCredentialFromEditingList(index) {
// Remove the credential from the credentials array
editingCredentials.splice(index, 1);
// Update the table body
updateEditingCredentialList();
}
function alreadyExists(username){
let isExists = false;
editingCredentials.forEach(function(cred){
if (cred.username == username){
isExists = true;
}
});
return isExists;
}
function cancelCredentialEdit(){
parent.hideSideWrapper(true);
}
function saveCredentials(){
$.ajax({
url: "/api/proxy/updateCredentials",
method: "POST",
data: {
ep: editingEndpoint.ep,
ptype: editingEndpoint.ept,
creds: JSON.stringify(editingCredentials)
},
success: function(data){
if (data.error != undefined){
parent.msgbox(data.error, false, 6000);
}else{
parent.msgbox("Credentials Updated");
parent.hideSideWrapper(true);
}
}
})
}
</script>
</body>
</html>

View File

@ -0,0 +1,102 @@
<!DOCTYPE html>
<html>
<head>
<!-- Notes: This should be open in its original path-->
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
<script src="../script/jquery-3.6.0.min.js"></script>
<script src="../script/semantic/semantic.min.js"></script>
</head>
<body>
<br>
<div class="ui container">
<div class="ui header">
<div class="content">
Intermediate Certificate Converter
<div class="sub header">Convert .cer files to .pem</div>
</div>
</div>
<div class="ui message">
<div class="header">
Why I need this?
</div>
<p>If you have 3 certificate files (2 x .cer + 1 x .key) provided by your ISP, you will need to merge the root and intermediate certificates in order to upload it to Zoraxy. This tool will automate the process for you.</p>
</div>
<div class="ui form">
<div class="field">
<label>Select Root Certificate</label>
<input type="file" id="rootCertificateInput">
</div>
<div class="field">
<label>Select Intermediate Certificate</label>
<input type="file" id="intermediateCertificateInput">
</div>
<div class="field">
<label>Export File Name (Optional)</label>
<input type="text" id="exportFilename" value="domain.pem">
</div>
<button class="ui basic button" onclick="convertCertificates()"> <i class="ui blue exchange icon"></i> Convert</button>
</div>
<br>
<button class="ui basic button" style="float: right;" onclick="parent.hideSideWrapper();"><i class="remove icon"></i> Cancel</button>
</div>
<script>
function mergeAndDownload(certificatesArray=["",""]){
if (certificatesArray[0] == "" || certificatesArray[1] == ""){
//Data not ready
return;
}
var filename = $("#exportFilename").val().trim();
if (filename == ""){
filename = "export.pem";
}
//Basically just concat both together. See https://github.com/tobychui/zoraxy/wiki/Import-your-own-certificate
generateAndDownload(certificatesArray[0] + '\n' + certificatesArray[1], filename);
}
function convertCertificates() {
var rootCertificateFile = document.getElementById('rootCertificateInput').files[0];
var intermediateCertificateFile = document.getElementById('intermediateCertificateInput').files[0];
var readerr = new FileReader();
var readeri = new FileReader();
let certificates = ["",""];
readerr.onload = function(event) {
var rootCertificateContent = event.target.result;
certificates[0] = rootCertificateContent;
mergeAndDownload(certificates);
console.log('Root Certificate Content:', rootCertificateContent);
};
readeri.onload = function(event) {
var intermediateCertificateContent = event.target.result;
certificates[1] = intermediateCertificateContent;
mergeAndDownload(certificates);
console.log('Intermediate Certificate Content:', intermediateCertificateContent);
};
readerr.readAsText(rootCertificateFile);
readeri.readAsText(intermediateCertificateFile);
}
function generateAndDownload(content, filename) {
var element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(content));
element.setAttribute('download', filename);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
</script>
</body>
</html>

View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
<script src="../script/jquery-3.6.0.min.js"></script>
<script src="../script/semantic/semantic.min.js"></script>
<style>
body{
height: 100%;
width: 100%;
}
</style>
</head>
<body>
<div class="ui active inverted dimmer">
<div class="ui text loader">Loading Snippet</div>
</div>
</body>
</html>

View File

@ -69,7 +69,7 @@ func HandleCheckSiteSupportTLS(w http.ResponseWriter, r *http.Request) {
Statistic Summary Statistic Summary
*/ */
//Handle conversion of statistic daily summary to country summary // Handle conversion of statistic daily summary to country summary
func HandleCountryDistrSummary(w http.ResponseWriter, r *http.Request) { func HandleCountryDistrSummary(w http.ResponseWriter, r *http.Request) {
requestClientCountry := map[string]int{} requestClientCountry := map[string]int{}
statisticCollector.DailySummary.RequestClientIp.Range(func(key, value interface{}) bool { statisticCollector.DailySummary.RequestClientIp.Range(func(key, value interface{}) bool {
@ -143,7 +143,7 @@ func GetUptimeTargetsFromReverseProxyRules(dp *dynamicproxy.Router) []*uptime.Ta
return UptimeTargets return UptimeTargets
} }
//Handle rendering up time monitor data // Handle rendering up time monitor data
func HandleUptimeMonitorListing(w http.ResponseWriter, r *http.Request) { func HandleUptimeMonitorListing(w http.ResponseWriter, r *http.Request) {
if uptimeMonitor != nil { if uptimeMonitor != nil {
uptimeMonitor.HandleUptimeLogRead(w, r) uptimeMonitor.HandleUptimeLogRead(w, r)
@ -153,7 +153,7 @@ func HandleUptimeMonitorListing(w http.ResponseWriter, r *http.Request) {
} }
} }
//Handle listing current registered mdns nodes // Handle listing current registered mdns nodes
func HandleMdnsListing(w http.ResponseWriter, r *http.Request) { func HandleMdnsListing(w http.ResponseWriter, r *http.Request) {
js, _ := json.Marshal(previousmdnsScanResults) js, _ := json.Marshal(previousmdnsScanResults)
utils.SendJSONResponse(w, string(js)) utils.SendJSONResponse(w, string(js))
@ -175,7 +175,7 @@ func HandleMdnsScanning(w http.ResponseWriter, r *http.Request) {
utils.SendJSONResponse(w, string(js)) utils.SendJSONResponse(w, string(js))
} }
//handle ip scanning // handle ip scanning
func HandleIpScan(w http.ResponseWriter, r *http.Request) { func HandleIpScan(w http.ResponseWriter, r *http.Request) {
cidr, err := utils.PostPara(r, "cidr") cidr, err := utils.PostPara(r, "cidr")
if err != nil { if err != nil {
@ -214,14 +214,16 @@ func HandleIpScan(w http.ResponseWriter, r *http.Request) {
} }
/* /*
WAKE ON LAN
Handle wake on LAN Handle wake on LAN
Support following methods Support following methods
/?set=xxx&name=xxx Record a new MAC address into the database /?set=xxx&name=xxx Record a new MAC address into the database
/?wake=xxx Wake a server given its MAC address /?wake=xxx Wake a server given its MAC address
/?del=xxx Delete a server given its MAC address /?del=xxx Delete a server given its MAC address
/ Default: list all recorded WoL MAC address / Default: list all recorded WoL MAC address
*/ */
func HandleWakeOnLan(w http.ResponseWriter, r *http.Request) { func HandleWakeOnLan(w http.ResponseWriter, r *http.Request) {
set, _ := utils.PostPara(r, "set") set, _ := utils.PostPara(r, "set")
del, _ := utils.PostPara(r, "del") del, _ := utils.PostPara(r, "del")
@ -297,3 +299,30 @@ func HandleWakeOnLan(w http.ResponseWriter, r *http.Request) {
utils.SendJSONResponse(w, string(js)) utils.SendJSONResponse(w, string(js))
} }
} }
/*
Zoraxy Host Info
*/
func HandleZoraxyInfo(w http.ResponseWriter, r *http.Request) {
type ZoraxyInfo struct {
Version string
NodeUUID string
Development bool
BootTime int64
EnableSshLoopback bool
ZerotierConnected bool
}
info := ZoraxyInfo{
Version: version,
NodeUUID: nodeUUID,
Development: development,
BootTime: bootTime,
EnableSshLoopback: *allowSshLoopback,
ZerotierConnected: ganManager.ControllerID != "",
}
js, _ := json.MarshalIndent(info, "", " ")
utils.SendJSONResponse(w, string(js))
}