Restructured proxy routing logic

- Moved virtual directory into host routing object
- Generalized root and hosts routing struct
- Optimized UI
This commit is contained in:
Toby Chui 2024-02-13 21:46:43 +08:00
parent 36e461795a
commit 3228789375
26 changed files with 1125 additions and 1156 deletions

View File

@ -57,8 +57,8 @@ func initAPIs() {
authRouter.HandleFunc("/api/proxy/listenPort80", HandleUpdatePort80Listener)
authRouter.HandleFunc("/api/proxy/requestIsProxied", HandleManagementProxyCheck)
//Reverse proxy root related APIs
authRouter.HandleFunc("/api/proxy/root/listOptions", HandleRootRouteOptionList)
authRouter.HandleFunc("/api/proxy/root/updateOptions", HandleRootRouteOptionsUpdate)
//authRouter.HandleFunc("/api/proxy/root/listOptions", HandleRootRouteOptionList)
//authRouter.HandleFunc("/api/proxy/root/updateOptions", HandleRootRouteOptionsUpdate)
//Reverse proxy auth related APIs
authRouter.HandleFunc("/api/proxy/auth/exceptions/list", ListProxyBasicAuthExceptionPaths)
authRouter.HandleFunc("/api/proxy/auth/exceptions/add", AddProxyBasicAuthExceptionPaths)

View File

@ -3,6 +3,7 @@ package main
import (
"archive/zip"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
@ -35,97 +36,119 @@ type Record struct {
BasicAuthExceptionRules []*dynamicproxy.BasicAuthExceptionRule
}
// Save a reverse proxy config record to file
func SaveReverseProxyConfigToFile(proxyConfigRecord *Record) error {
//TODO: Make this accept new def types
os.MkdirAll("./conf/proxy/", 0775)
filename := getFilenameFromRootName(proxyConfigRecord.Rootname)
//Generate record
thisRecord := proxyConfigRecord
//Write to file
js, _ := json.MarshalIndent(thisRecord, "", " ")
return os.WriteFile(filepath.Join("./conf/proxy/", filename), js, 0775)
}
// Save a running reverse proxy endpoint to file (with automatic endpoint to record conversion)
func SaveReverseProxyEndpointToFile(proxyEndpoint *dynamicproxy.ProxyEndpoint) error {
recordToSave, err := ConvertProxyEndpointToRecord(proxyEndpoint)
/*
Load Reverse Proxy Config from file and append it to current runtime proxy router
*/
func LoadReverseProxyConfig(configFilepath string) error {
//Load the config file from disk
endpointConfig, err := os.ReadFile(configFilepath)
if err != nil {
return err
}
return SaveReverseProxyConfigToFile(recordToSave)
}
func RemoveReverseProxyConfigFile(rootname string) error {
filename := getFilenameFromRootName(rootname)
removePendingFile := strings.ReplaceAll(filepath.Join("./conf/proxy/", filename), "\\", "/")
SystemWideLogger.Println("Config Removed: ", removePendingFile)
if utils.FileExists(removePendingFile) {
err := os.Remove(removePendingFile)
if err != nil {
SystemWideLogger.PrintAndLog("Proxy", "Unabel to remove config file", err)
return err
}
//Parse it into dynamic proxy endpoint
thisConfigEndpoint := dynamicproxy.ProxyEndpoint{}
err = json.Unmarshal(endpointConfig, &thisConfigEndpoint)
if err != nil {
return err
}
//File already gone
//Matching domain not set. Assume root
if thisConfigEndpoint.RootOrMatchingDomain == "" {
thisConfigEndpoint.RootOrMatchingDomain = "/"
}
if thisConfigEndpoint.ProxyType == dynamicproxy.ProxyType_Root {
//This is a root config file
rootProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&thisConfigEndpoint)
if err != nil {
return err
}
dynamicProxyRouter.SetProxyRouteAsRoot(rootProxyEndpoint)
} else if thisConfigEndpoint.ProxyType == dynamicproxy.ProxyType_Host {
//This is a host config file
readyProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&thisConfigEndpoint)
if err != nil {
return err
}
dynamicProxyRouter.AddProxyRouteToRuntime(readyProxyEndpoint)
} else {
return errors.New("not supported proxy type")
}
SystemWideLogger.PrintAndLog("Proxy", thisConfigEndpoint.RootOrMatchingDomain+" -> "+thisConfigEndpoint.Domain+" routing rule loaded", nil)
return nil
}
// Return ptype, rootname and proxyTarget, error if any
func LoadReverseProxyConfig(filename string) (*Record, error) {
thisRecord := Record{
ProxyType: "",
Rootname: "",
ProxyTarget: "",
UseTLS: false,
func filterProxyConfigFilename(filename string) string {
//Filter out wildcard characters
filename = strings.ReplaceAll(filename, "*", "(ST)")
filename = strings.ReplaceAll(filename, "?", "(QM)")
filename = strings.ReplaceAll(filename, "[", "(OB)")
filename = strings.ReplaceAll(filename, "]", "(CB)")
filename = strings.ReplaceAll(filename, "#", "(HT)")
return filepath.ToSlash(filename)
}
func SaveReverseProxyConfig(endpoint *dynamicproxy.ProxyEndpoint) error {
//Get filename for saving
filename := filepath.Join("./conf/proxy/", endpoint.RootOrMatchingDomain+".config")
if endpoint.ProxyType == dynamicproxy.ProxyType_Root {
filename = "./conf/proxy/root.config"
}
filename = filterProxyConfigFilename(filename)
//Save config to file
js, err := json.MarshalIndent(endpoint, "", " ")
if err != nil {
return err
}
os.WriteFile(filename, js, 0775)
return nil
}
func RemoveReverseProxyConfig(endpoint string) error {
filename := filepath.Join("./conf/proxy/", endpoint+".config")
if endpoint == "/" {
filename = "./conf/proxy/root.config"
}
filename = filterProxyConfigFilename(filename)
if !utils.FileExists(filename) {
return errors.New("target endpoint not exists")
}
return os.Remove(filename)
}
// Get the default root config that point to the internal static web server
// this will be used if root config is not found (new deployment / missing root.config file)
func GetDefaultRootConfig() (*dynamicproxy.ProxyEndpoint, error) {
//Default settings
rootProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&dynamicproxy.ProxyEndpoint{
ProxyType: dynamicproxy.ProxyType_Root,
RootOrMatchingDomain: "/",
Domain: "127.0.0.1:" + staticWebServer.GetListeningPort(),
RequireTLS: false,
BypassGlobalTLS: false,
SkipTlsValidation: false,
SkipCertValidations: false,
VirtualDirectories: []*dynamicproxy.ProxyEndpoint{},
RequireBasicAuth: false,
BasicAuthCredentials: []*dynamicproxy.BasicAuthCredentials{},
BasicAuthExceptionRules: []*dynamicproxy.BasicAuthExceptionRule{},
}
configContent, err := os.ReadFile(filename)
DefaultSiteOption: dynamicproxy.DefaultSite_InternalStaticWebServer,
DefaultSiteValue: "",
})
if err != nil {
return &thisRecord, err
return nil, err
}
//Unmarshal the content into config
err = json.Unmarshal(configContent, &thisRecord)
if err != nil {
return &thisRecord, err
}
//Return it
return &thisRecord, nil
}
// Convert a running proxy endpoint object into a save-able record struct
func ConvertProxyEndpointToRecord(targetProxyEndpoint *dynamicproxy.ProxyEndpoint) (*Record, error) {
thisProxyConfigRecord := Record{
ProxyType: targetProxyEndpoint.GetProxyTypeString(),
Rootname: targetProxyEndpoint.RootOrMatchingDomain,
ProxyTarget: targetProxyEndpoint.Domain,
UseTLS: targetProxyEndpoint.RequireTLS,
BypassGlobalTLS: targetProxyEndpoint.BypassGlobalTLS,
SkipTlsValidation: targetProxyEndpoint.SkipCertValidations,
RequireBasicAuth: targetProxyEndpoint.RequireBasicAuth,
BasicAuthCredentials: targetProxyEndpoint.BasicAuthCredentials,
BasicAuthExceptionRules: targetProxyEndpoint.BasicAuthExceptionRules,
}
return &thisProxyConfigRecord, nil
}
func getFilenameFromRootName(rootname string) string {
//Generate a filename for this rootname
filename := strings.ReplaceAll(rootname, ".", "_")
filename = strings.ReplaceAll(filename, "/", "-")
filename = filename + ".config"
return filename
return rootProxyEndpoint, nil
}
/*

View File

@ -49,9 +49,9 @@ var logOutputToFile = flag.Bool("log", true, "Log terminal output to file")
var (
name = "Zoraxy"
version = "2.6.8"
version = "3.0.0"
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()
/*

View File

@ -3,7 +3,6 @@ package dynamicproxy
import (
_ "embed"
"errors"
"log"
"net/http"
"net/url"
"os"
@ -80,38 +79,26 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
/*
Subdomain Routing
Host Routing
*/
if strings.Contains(r.Host, ".") {
//This might be a subdomain. See if there are any subdomain proxy router for this
sep := h.Parent.getSubdomainProxyEndpointFromHostname(domainOnly)
if sep != nil {
if sep.RequireBasicAuth {
err := h.handleBasicAuthRouting(w, r, sep)
if err != nil {
return
}
}
h.subdomainRequest(w, r, sep)
return
}
}
/*
Virtual Directory Routing
*/
//Clean up the request URI
proxyingPath := strings.TrimSpace(r.RequestURI)
targetProxyEndpoint := h.Parent.getTargetProxyEndpointFromRequestURI(proxyingPath)
if targetProxyEndpoint != nil {
if targetProxyEndpoint.RequireBasicAuth {
err := h.handleBasicAuthRouting(w, r, targetProxyEndpoint)
sep := h.Parent.getProxyEndpointFromHostname(domainOnly)
if sep != nil {
if sep.RequireBasicAuth {
err := h.handleBasicAuthRouting(w, r, sep)
if err != nil {
return
}
}
h.proxyRequest(w, r, targetProxyEndpoint)
} else if !strings.HasSuffix(proxyingPath, "/") {
h.hostRequest(w, r, sep)
return
}
/*
Root Router Handling
*/
//Clean up the request URI
proxyingPath := strings.TrimSpace(r.RequestURI)
if !strings.HasSuffix(proxyingPath, "/") {
potentialProxtEndpoint := h.Parent.getTargetProxyEndpointFromRequestURI(proxyingPath + "/")
if potentialProxtEndpoint != nil {
//Missing tailing slash. Redirect to target proxy endpoint
@ -136,52 +123,45 @@ Once entered this routing segment, the root routing options will take over
for the routing logic.
*/
func (h *ProxyHandler) handleRootRouting(w http.ResponseWriter, r *http.Request) {
domainOnly := r.Host
if strings.Contains(r.Host, ":") {
hostPath := strings.Split(r.Host, ":")
domainOnly = hostPath[0]
}
if h.Parent.RootRoutingOptions.EnableRedirectForUnsetRules {
//Route to custom domain
if h.Parent.RootRoutingOptions.UnsetRuleRedirectTarget == "" {
//Not set. Redirect to first level of domain redirectable
fld, err := h.getTopLevelRedirectableDomain(domainOnly)
if err != nil {
//Redirect to proxy root
h.proxyRequest(w, r, h.Parent.Root)
} else {
log.Println("[Router] Redirecting request from " + domainOnly + " to " + fld)
h.logRequest(r, false, 307, "root-redirect", domainOnly)
http.Redirect(w, r, fld, http.StatusTemporaryRedirect)
}
return
} else if h.isTopLevelRedirectableDomain(domainOnly) {
//This is requesting a top level private domain that should be serving root
h.proxyRequest(w, r, h.Parent.Root)
} else {
//Validate the redirection target URL
parsedURL, err := url.Parse(h.Parent.RootRoutingOptions.UnsetRuleRedirectTarget)
if err != nil {
//Error when parsing target. Send to root
h.proxyRequest(w, r, h.Parent.Root)
return
}
hostname := parsedURL.Hostname()
if domainOnly != hostname {
//Redirect to target
h.logRequest(r, false, 307, "root-redirect", domainOnly)
http.Redirect(w, r, h.Parent.RootRoutingOptions.UnsetRuleRedirectTarget, http.StatusTemporaryRedirect)
return
} else {
//Loopback request due to bad settings (Shd leave it empty)
//Forward it to root proxy
h.proxyRequest(w, r, h.Parent.Root)
}
//Get the proxy root config
proot := h.Parent.Root
switch proot.DefaultSiteOption {
case DefaultSite_InternalStaticWebServer:
fallthrough
case DefaultSite_ReverseProxy:
//They both share the same behavior
h.vdirRequest(w, r, h.Parent.Root)
case DefaultSite_Redirect:
redirectTarget := strings.TrimSpace(proot.DefaultSiteValue)
if redirectTarget == "" {
redirectTarget = "about:blank"
}
} else {
//Route to root
h.proxyRequest(w, r, h.Parent.Root)
//Check if it is an infinite loopback redirect
parsedURL, err := url.Parse(proot.DefaultSiteValue)
if err != nil {
//Error when parsing target. Send to root
h.vdirRequest(w, r, h.Parent.Root)
return
}
hostname := parsedURL.Hostname()
if hostname == domainOnly {
h.logRequest(r, false, 500, "root-redirect", domainOnly)
http.Error(w, "Loopback redirects due to invalid settings", 500)
return
}
h.logRequest(r, false, 307, "root-redirect", domainOnly)
http.Redirect(w, r, redirectTarget, http.StatusTemporaryRedirect)
case DefaultSite_NotFoundPage:
http.NotFound(w, r)
}
}

View File

@ -26,10 +26,6 @@ func (h *ProxyHandler) handleBasicAuthRouting(w http.ResponseWriter, r *http.Req
}
}
proxyType := "vdir-auth"
if pe.ProxyType == ProxyType_Subdomain {
proxyType = "subd-auth"
}
u, p, ok := r.BasicAuth()
if !ok {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
@ -48,7 +44,7 @@ func (h *ProxyHandler) handleBasicAuthRouting(w http.ResponseWriter, r *http.Req
}
if !matchingFound {
h.logRequest(r, false, 401, proxyType, pe.Domain)
h.logRequest(r, false, 401, "host", pe.Domain)
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
w.WriteHeader(401)
return errors.New("unauthorized")

View File

@ -22,15 +22,13 @@ import (
func NewDynamicProxy(option RouterOption) (*Router, error) {
proxyMap := sync.Map{}
domainMap := sync.Map{}
thisRouter := Router{
Option: &option,
ProxyEndpoints: &proxyMap,
SubdomainEndpoint: &domainMap,
Running: false,
server: nil,
routingRules: []*RoutingRule{},
tldMap: map[string]int{},
Option: &option,
ProxyEndpoints: &proxyMap,
Running: false,
server: nil,
routingRules: []*RoutingRule{},
tldMap: map[string]int{},
}
thisRouter.mux = &ProxyHandler{
@ -84,13 +82,6 @@ func (router *Router) StartProxyService() error {
return errors.New("Reverse proxy router root not set")
}
//Load root options from file
loadedRootOption, err := loadRootRoutingOptionsFromFile()
if err != nil {
return err
}
router.RootRoutingOptions = loadedRootOption
minVersion := tls.VersionTLS10
if router.Option.ForceTLSLatest {
minVersion = tls.VersionTLS12
@ -129,7 +120,7 @@ func (router *Router) StartProxyService() error {
hostPath := strings.Split(r.Host, ":")
domainOnly = hostPath[0]
}
sep := router.getSubdomainProxyEndpointFromHostname(domainOnly)
sep := router.getProxyEndpointFromHostname(domainOnly)
if sep != nil && sep.BypassGlobalTLS {
//Allow routing via non-TLS handler
originalHostHeader := r.Host
@ -140,7 +131,7 @@ func (router *Router) StartProxyService() error {
r.URL, _ = url.Parse(originalHostHeader)
}
sep.Proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
sep.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
ProxyDomain: sep.Domain,
OriginalHost: originalHostHeader,
UseTLS: sep.RequireTLS,
@ -280,128 +271,17 @@ func (router *Router) IsProxiedSubdomain(r *http.Request) bool {
hostname = r.Host
}
hostname = strings.Split(hostname, ":")[0]
subdEndpoint := router.getSubdomainProxyEndpointFromHostname(hostname)
subdEndpoint := router.getProxyEndpointFromHostname(hostname)
return subdEndpoint != nil
}
/*
Add an URL into a custom proxy services
*/
func (router *Router) AddVirtualDirectoryProxyService(options *VdirOptions) error {
domain := options.Domain
if domain[len(domain)-1:] == "/" {
domain = domain[:len(domain)-1]
}
/*
if rootname[len(rootname)-1:] == "/" {
rootname = rootname[:len(rootname)-1]
}
*/
webProxyEndpoint := domain
if options.RequireTLS {
webProxyEndpoint = "https://" + webProxyEndpoint
} else {
webProxyEndpoint = "http://" + webProxyEndpoint
}
//Create a new proxy agent for this root
path, err := url.Parse(webProxyEndpoint)
if err != nil {
return err
}
proxy := dpcore.NewDynamicProxyCore(path, options.RootName, options.SkipCertValidations)
endpointObject := ProxyEndpoint{
ProxyType: ProxyType_Vdir,
RootOrMatchingDomain: options.RootName,
Domain: domain,
RequireTLS: options.RequireTLS,
SkipCertValidations: options.SkipCertValidations,
RequireBasicAuth: options.RequireBasicAuth,
BasicAuthCredentials: options.BasicAuthCredentials,
BasicAuthExceptionRules: options.BasicAuthExceptionRules,
Proxy: proxy,
}
router.ProxyEndpoints.Store(options.RootName, &endpointObject)
log.Println("Registered Proxy Rule: ", options.RootName+" to "+domain)
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")
}
targetProxy := proxy.(*ProxyEndpoint)
targetProxy.parent = router
return targetProxy, nil
} else if ptype == "subd" {
proxy, ok := router.SubdomainEndpoint.Load(key)
if !ok {
return nil, errors.New("target proxy not found")
}
targetProxy := proxy.(*ProxyEndpoint)
targetProxy.parent = router
return targetProxy, nil
}
return nil, errors.New("unsupported ptype")
}
/*
Add an default router for the proxy server
*/
func (router *Router) SetRootProxy(options *RootOptions) error {
proxyLocation := options.ProxyLocation
if proxyLocation[len(proxyLocation)-1:] == "/" {
proxyLocation = proxyLocation[:len(proxyLocation)-1]
}
webProxyEndpoint := proxyLocation
if options.RequireTLS {
webProxyEndpoint = "https://" + webProxyEndpoint
} else {
webProxyEndpoint = "http://" + webProxyEndpoint
}
//Create a new proxy agent for this root
path, err := url.Parse(webProxyEndpoint)
if err != nil {
return err
}
proxy := dpcore.NewDynamicProxyCore(path, "", options.SkipCertValidations)
rootEndpoint := ProxyEndpoint{
ProxyType: ProxyType_Vdir,
RootOrMatchingDomain: "/",
Domain: proxyLocation,
RequireTLS: options.RequireTLS,
SkipCertValidations: options.SkipCertValidations,
RequireBasicAuth: options.RequireBasicAuth,
BasicAuthCredentials: options.BasicAuthCredentials,
BasicAuthExceptionRules: options.BasicAuthExceptionRules,
Proxy: proxy,
}
router.Root = &rootEndpoint
return nil
}
// Helpers to export the syncmap for easier processing
func (r *Router) GetSDProxyEndpointsAsMap() map[string]*ProxyEndpoint {
m := make(map[string]*ProxyEndpoint)
r.SubdomainEndpoint.Range(func(key, value interface{}) bool {
k, ok := key.(string)
func (router *Router) LoadProxy(matchingDomain string) (*ProxyEndpoint, error) {
var targetProxyEndpoint *ProxyEndpoint
router.ProxyEndpoints.Range(func(key, value interface{}) bool {
key, ok := key.(string)
if !ok {
return true
}
@ -409,13 +289,32 @@ func (r *Router) GetSDProxyEndpointsAsMap() map[string]*ProxyEndpoint {
if !ok {
return true
}
m[k] = v
if key == matchingDomain {
targetProxyEndpoint = v
}
return true
})
return m
if targetProxyEndpoint == nil {
return nil, errors.New("target routing rule not found")
}
return targetProxyEndpoint, nil
}
func (r *Router) GetVDProxyEndpointsAsMap() map[string]*ProxyEndpoint {
// Deep copy a proxy endpoint, excluding runtime paramters
func CopyEndpoint(endpoint *ProxyEndpoint) *ProxyEndpoint {
js, _ := json.Marshal(endpoint)
newProxyEndpoint := ProxyEndpoint{}
err := json.Unmarshal(js, &newProxyEndpoint)
if err != nil {
return nil
}
return &newProxyEndpoint
}
func (r *Router) GetProxyEndpointsAsMap() map[string]*ProxyEndpoint {
m := make(map[string]*ProxyEndpoint)
r.ProxyEndpoints.Range(func(key, value interface{}) bool {
k, ok := key.(string)

View File

@ -0,0 +1,65 @@
package dynamicproxy
import (
"errors"
"net/url"
"strings"
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
)
// Prepare proxy route generate a proxy handler service object for your endpoint
func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint, error) {
//Filter the tailing slash if any
domain := endpoint.Domain
if domain[len(domain)-1:] == "/" {
domain = domain[:len(domain)-1]
}
endpoint.Domain = domain
//Parse the web proxy endpoint
webProxyEndpoint := domain
if !strings.HasPrefix("http://", domain) && !strings.HasPrefix("https://", domain) {
//TLS is not hardcoded in proxy target domain
if endpoint.RequireTLS {
webProxyEndpoint = "https://" + webProxyEndpoint
} else {
webProxyEndpoint = "http://" + webProxyEndpoint
}
}
//Create a new proxy agent for this root
path, err := url.Parse(webProxyEndpoint)
if err != nil {
return nil, err
}
//Create the proxy routing handler
proxy := dpcore.NewDynamicProxyCore(path, "", endpoint.SkipCertValidations)
endpoint.proxy = proxy
endpoint.parent = router
return endpoint, nil
}
// Add Proxy Route to current runtime. Call to PrepareProxyRoute before adding to runtime
func (router *Router) AddProxyRouteToRuntime(endpoint *ProxyEndpoint) error {
if endpoint.proxy == nil {
//This endpoint is not prepared
return errors.New("proxy endpoint not ready. Use PrepareProxyRoute before adding to runtime")
}
// Push record into running subdomain endpoints
router.ProxyEndpoints.Store(endpoint.RootOrMatchingDomain, endpoint)
return nil
}
// Set given Proxy Route as Root. Call to PrepareProxyRoute before adding to runtime
func (router *Router) SetProxyRouteAsRoot(endpoint *ProxyEndpoint) error {
if endpoint.proxy == nil {
//This endpoint is not prepared
return errors.New("proxy endpoint not ready. Use PrepareProxyRoute before adding to runtime")
}
// Push record into running root endpoints
router.Root = endpoint
return nil
}

View File

@ -1,7 +1,5 @@
package dynamicproxy
import "errors"
/*
ProxyEndpoint.go
author: tobychui
@ -12,54 +10,20 @@ import "errors"
Most of the functions are implemented in dynamicproxy.go
*/
//Get the string version of proxy type
func (ep *ProxyEndpoint) GetProxyTypeString() string {
if ep.ProxyType == ProxyType_Subdomain {
return "subd"
} else if ep.ProxyType == ProxyType_Vdir {
return "vdir"
}
return "unknown"
}
//Update change in the current running proxy endpoint config
// Update change in the current running proxy endpoint config
func (ep *ProxyEndpoint) UpdateToRuntime() {
if ep.IsVdir() {
ep.parent.ProxyEndpoints.Store(ep.RootOrMatchingDomain, ep)
} else if ep.IsSubDomain() {
ep.parent.SubdomainEndpoint.Store(ep.RootOrMatchingDomain, ep)
}
ep.parent.ProxyEndpoints.Store(ep.RootOrMatchingDomain, ep)
}
//Return true if the endpoint type is virtual directory
func (ep *ProxyEndpoint) IsVdir() bool {
return ep.ProxyType == ProxyType_Vdir
}
//Return true if the endpoint type is subdomain
func (ep *ProxyEndpoint) IsSubDomain() bool {
return ep.ProxyType == ProxyType_Subdomain
}
//Remove this proxy endpoint from running proxy endpoint list
// Remove this proxy endpoint from running proxy endpoint list
func (ep *ProxyEndpoint) Remove() error {
//fmt.Println(ptype, key)
if ep.IsVdir() {
ep.parent.ProxyEndpoints.Delete(ep.RootOrMatchingDomain)
return nil
} else if ep.IsSubDomain() {
ep.parent.SubdomainEndpoint.Delete(ep.RootOrMatchingDomain)
return nil
}
return errors.New("invalid or unsupported type")
ep.parent.ProxyEndpoints.Delete(ep.RootOrMatchingDomain)
return nil
}
//ProxyEndpoint remove provide global access by key
func (router *Router) RemoveProxyEndpointByRootname(proxyType string, rootnameOrMatchingDomain string) error {
targetEpt, err := router.LoadProxy(proxyType, rootnameOrMatchingDomain)
// ProxyEndpoint remove provide global access by key
func (router *Router) RemoveProxyEndpointByRootname(rootnameOrMatchingDomain string) error {
targetEpt, err := router.LoadProxy(rootnameOrMatchingDomain)
if err != nil {
return err
}

View File

@ -6,6 +6,7 @@ import (
"net"
"net/http"
"net/url"
"path/filepath"
"strings"
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
@ -28,13 +29,28 @@ func (router *Router) getTargetProxyEndpointFromRequestURI(requestURI string) *P
return targetProxyEndpoint
}
func (router *Router) getSubdomainProxyEndpointFromHostname(hostname string) *ProxyEndpoint {
func (router *Router) getProxyEndpointFromHostname(hostname string) *ProxyEndpoint {
var targetSubdomainEndpoint *ProxyEndpoint = nil
ep, ok := router.SubdomainEndpoint.Load(hostname)
ep, ok := router.ProxyEndpoints.Load(hostname)
if ok {
targetSubdomainEndpoint = ep.(*ProxyEndpoint)
}
//No hit. Try with wildcard
router.ProxyEndpoints.Range(func(k, v interface{}) bool {
ep := v.(*ProxyEndpoint)
match, err := filepath.Match(ep.RootOrMatchingDomain, hostname)
if err != nil {
//Continue
return true
}
if match {
targetSubdomainEndpoint = ep
return false
}
return true
})
return targetSubdomainEndpoint
}
@ -54,8 +70,8 @@ func (router *Router) rewriteURL(rooturl string, requestURL string) string {
return rewrittenURL
}
// Handle subdomain request
func (h *ProxyHandler) subdomainRequest(w http.ResponseWriter, r *http.Request, target *ProxyEndpoint) {
// Handle host request
func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, target *ProxyEndpoint) {
r.Header.Set("X-Forwarded-Host", r.Host)
r.Header.Set("X-Forwarded-Server", "zoraxy-"+h.Parent.Option.HostUUID)
requestURL := r.URL.String()
@ -89,7 +105,7 @@ func (h *ProxyHandler) subdomainRequest(w http.ResponseWriter, r *http.Request,
r.URL, _ = url.Parse(originalHostHeader)
}
err := target.Proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
ProxyDomain: target.Domain,
OriginalHost: originalHostHeader,
UseTLS: target.RequireTLS,
@ -113,7 +129,7 @@ func (h *ProxyHandler) subdomainRequest(w http.ResponseWriter, r *http.Request,
}
// Handle vdir type request
func (h *ProxyHandler) proxyRequest(w http.ResponseWriter, r *http.Request, target *ProxyEndpoint) {
func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, target *ProxyEndpoint) {
rewriteURL := h.Parent.rewriteURL(target.RootOrMatchingDomain, r.RequestURI)
r.URL, _ = url.Parse(rewriteURL)
@ -144,7 +160,7 @@ func (h *ProxyHandler) proxyRequest(w http.ResponseWriter, r *http.Request, targ
r.URL, _ = url.Parse(originalHostHeader)
}
err := target.Proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
ProxyDomain: target.Domain,
OriginalHost: originalHostHeader,
UseTLS: target.RequireTLS,

View File

@ -14,8 +14,9 @@ import (
)
const (
ProxyType_Subdomain = 0
ProxyType_Vdir = 1
ProxyType_Root = 0
ProxyType_Host = 1
ProxyType_Vdir = 2
)
type ProxyHandler struct {
@ -37,16 +38,14 @@ type RouterOption struct {
}
type Router struct {
Option *RouterOption
ProxyEndpoints *sync.Map
SubdomainEndpoint *sync.Map
Running bool
Root *ProxyEndpoint
RootRoutingOptions *RootRoutingOptions
mux http.Handler
server *http.Server
tlsListener net.Listener
routingRules []*RoutingRule
Option *RouterOption
ProxyEndpoints *sync.Map
Running bool
Root *ProxyEndpoint
mux http.Handler
server *http.Server
tlsListener net.Listener
routingRules []*RoutingRule
tlsRedirectStop chan bool //Stop channel for tls redirection server
tldMap map[string]int //Top level domain map, see tld.json
@ -69,63 +68,48 @@ type BasicAuthExceptionRule struct {
PathPrefix string
}
// A proxy endpoint record
// A proxy endpoint record, a general interface for handling inbound routing
type ProxyEndpoint struct {
ProxyType int //The type of this proxy, see const def
RootOrMatchingDomain string //Root for vdir or Matching domain for subd, also act as key
Domain string //Domain or IP to proxy to
RequireTLS bool //Target domain require TLS
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
SkipCertValidations bool //Set to true to accept self signed certs
RequireBasicAuth bool //Set to true to request basic auth before proxy
BasicAuthCredentials []*BasicAuthCredentials `json:"-"` //Basic auth credentials
BasicAuthExceptionRules []*BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target
Proxy *dpcore.ReverseProxy `json:"-"`
ProxyType int //The type of this proxy, see const def
RootOrMatchingDomain string //Root for vdir or Matching domain for subd, also act as key
Domain string //Domain or IP to proxy to
//TLS/SSL Related
RequireTLS bool //Target domain require TLS
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
SkipCertValidations bool //Set to true to accept self signed certs
//Virtual Directories
VirtualDirectories []*ProxyEndpoint
//Authentication
RequireBasicAuth bool //Set to true to request basic auth before proxy
BasicAuthCredentials []*BasicAuthCredentials //Basic auth credentials
BasicAuthExceptionRules []*BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target
//Fallback routing logic
DefaultSiteOption int //Fallback routing logic options
DefaultSiteValue string //Fallback routing target, optional
//Internal Logic Elements
parent *Router
proxy *dpcore.ReverseProxy `json:"-"`
}
/*
Routing type specific interface
These are options that only avaible for a specific interface
when running, these are converted into "ProxyEndpoint" objects
for more generic routing logic
*/
// Root options are those that are required for reverse proxy handler to work
type RootOptions struct {
ProxyLocation string //Proxy Root target, all unset traffic will be forward to here
RequireTLS bool //Proxy root target require TLS connection (not recommended)
BypassGlobalTLS bool //Bypass global TLS setting and make root http only (not recommended)
SkipCertValidations bool //Skip cert validation, suitable for self-signed certs, CURRENTLY NOT USED
//Basic Auth Related
RequireBasicAuth bool //Require basic auth, CURRENTLY NOT USED
BasicAuthCredentials []*BasicAuthCredentials
BasicAuthExceptionRules []*BasicAuthExceptionRule
}
// Additional options are here for letting router knows how to route exception cases for root
type RootRoutingOptions struct {
//Root only configs
EnableRedirectForUnsetRules bool //Force unset rules to redirect to custom domain
UnsetRuleRedirectTarget string //Custom domain to redirect to for unset rules
}
type VdirOptions struct {
RootName string
Domain string
RequireTLS bool
BypassGlobalTLS bool
SkipCertValidations bool
RequireBasicAuth bool
BasicAuthCredentials []*BasicAuthCredentials
BasicAuthExceptionRules []*BasicAuthExceptionRule
}
type SubdOptions struct {
MatchingDomain string
Domain string
RequireTLS bool
BypassGlobalTLS bool
SkipCertValidations bool
RequireBasicAuth bool
BasicAuthCredentials []*BasicAuthCredentials
BasicAuthExceptionRules []*BasicAuthExceptionRule
}
const (
DefaultSite_InternalStaticWebServer = 0
DefaultSite_ReverseProxy = 1
DefaultSite_Redirect = 2
DefaultSite_NotFoundPage = 3
)
/*
Web Templates

View File

@ -89,7 +89,7 @@ func (ws *WebServer) RestorePreviousState() {
ws.option.EnableDirectoryListing = enableDirList
//Check the running state
webservRunning := false
webservRunning := true
ws.option.Sysdb.Read("webserv", "enabled", &webservRunning)
if webservRunning {
ws.Start()
@ -124,6 +124,11 @@ func (ws *WebServer) ChangePort(port string) error {
return nil
}
// Get current using port in options
func (ws *WebServer) GetListeningPort() string {
return ws.option.Port
}
// Start starts the web server.
func (ws *WebServer) Start() error {
ws.mu.Lock()

View File

@ -2,6 +2,7 @@ package main
import (
"encoding/json"
"fmt"
"net/http"
"path/filepath"
"sort"
@ -21,6 +22,9 @@ var (
// Add user customizable reverse proxy
func ReverseProxtInit() {
/*
Load Reverse Proxy Global Settings
*/
inboundPort := 80
if sysdb.KeyExists("settings", "inbound") {
sysdb.Read("settings", "inbound", &inboundPort)
@ -63,6 +67,12 @@ func ReverseProxtInit() {
SystemWideLogger.Println("Force HTTPS mode disabled")
}
/*
Create a new proxy object
The DynamicProxy is the parent of all reverse proxy handlers,
use for managemening and provide functions to access proxy handlers
*/
dprouter, err := dynamicproxy.NewDynamicProxy(dynamicproxy.RouterOption{
HostUUID: nodeUUID,
Port: inboundPort,
@ -83,45 +93,28 @@ func ReverseProxtInit() {
dynamicProxyRouter = dprouter
//Load all conf from files
/*
Load all conf from files
*/
confs, _ := filepath.Glob("./conf/proxy/*.config")
for _, conf := range confs {
record, err := LoadReverseProxyConfig(conf)
err := LoadReverseProxyConfig(conf)
if err != nil {
SystemWideLogger.PrintAndLog("Proxy", "Failed to load config file: "+filepath.Base(conf), err)
return
}
}
if record.ProxyType == "root" {
dynamicProxyRouter.SetRootProxy(&dynamicproxy.RootOptions{
ProxyLocation: record.ProxyTarget,
RequireTLS: record.UseTLS,
})
} else if record.ProxyType == "subd" {
dynamicProxyRouter.AddSubdomainRoutingService(&dynamicproxy.SubdOptions{
MatchingDomain: record.Rootname,
Domain: record.ProxyTarget,
RequireTLS: record.UseTLS,
BypassGlobalTLS: record.BypassGlobalTLS,
SkipCertValidations: record.SkipTlsValidation,
RequireBasicAuth: record.RequireBasicAuth,
BasicAuthCredentials: record.BasicAuthCredentials,
BasicAuthExceptionRules: record.BasicAuthExceptionRules,
})
} else if record.ProxyType == "vdir" {
dynamicProxyRouter.AddVirtualDirectoryProxyService(&dynamicproxy.VdirOptions{
RootName: record.Rootname,
Domain: record.ProxyTarget,
RequireTLS: record.UseTLS,
BypassGlobalTLS: record.BypassGlobalTLS,
SkipCertValidations: record.SkipTlsValidation,
RequireBasicAuth: record.RequireBasicAuth,
BasicAuthCredentials: record.BasicAuthCredentials,
BasicAuthExceptionRules: record.BasicAuthExceptionRules,
})
} else {
SystemWideLogger.PrintAndLog("Proxy", "Unsupported endpoint type: "+record.ProxyType+". Skipping "+filepath.Base(conf), nil)
if dynamicProxyRouter.Root == nil {
//Root config not set (new deployment?), use internal static web server as root
defaultRootRouter, err := GetDefaultRootConfig()
if err != nil {
SystemWideLogger.PrintAndLog("Proxy", "Failed to generate default root routing", err)
return
}
dynamicProxyRouter.SetProxyRouteAsRoot(defaultRootRouter)
}
//Start Service
@ -173,7 +166,7 @@ func ReverseProxyHandleOnOff(w http.ResponseWriter, r *http.Request) {
}
func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
eptype, err := utils.PostPara(r, "type") //Support root, vdir and subd
eptype, err := utils.PostPara(r, "type") //Support root and host
if err != nil {
utils.SendErrorResponse(w, "type not defined")
return
@ -241,73 +234,96 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
}
}
rootname := ""
if eptype == "vdir" {
vdir, err := utils.PostPara(r, "rootname")
if err != nil {
utils.SendErrorResponse(w, "vdir not defined")
return
}
//Vdir must start with /
if !strings.HasPrefix(vdir, "/") {
vdir = "/" + vdir
}
rootname = vdir
thisOption := dynamicproxy.VdirOptions{
RootName: vdir,
Domain: endpoint,
RequireTLS: useTLS,
BypassGlobalTLS: useBypassGlobalTLS,
SkipCertValidations: skipTlsValidation,
RequireBasicAuth: requireBasicAuth,
BasicAuthCredentials: basicAuthCredentials,
}
dynamicProxyRouter.AddVirtualDirectoryProxyService(&thisOption)
} else if eptype == "subd" {
subdomain, err := utils.PostPara(r, "rootname")
var proxyEndpointCreated *dynamicproxy.ProxyEndpoint
if eptype == "host" {
rootOrMatchingDomain, err := utils.PostPara(r, "rootname")
if err != nil {
utils.SendErrorResponse(w, "subdomain not defined")
return
}
rootname = subdomain
thisOption := dynamicproxy.SubdOptions{
MatchingDomain: subdomain,
thisProxyEndpoint := dynamicproxy.ProxyEndpoint{
//I/O
ProxyType: dynamicproxy.ProxyType_Host,
RootOrMatchingDomain: rootOrMatchingDomain,
Domain: endpoint,
//TLS
RequireTLS: useTLS,
BypassGlobalTLS: useBypassGlobalTLS,
SkipCertValidations: skipTlsValidation,
//VDir
VirtualDirectories: []*dynamicproxy.ProxyEndpoint{},
//Auth
RequireBasicAuth: requireBasicAuth,
BasicAuthCredentials: basicAuthCredentials,
BasicAuthExceptionRules: []*dynamicproxy.BasicAuthExceptionRule{},
DefaultSiteOption: 0,
DefaultSiteValue: "",
}
preparedEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&thisProxyEndpoint)
if err != nil {
utils.SendErrorResponse(w, "unable to prepare proxy route to target endpoint: "+err.Error())
return
}
dynamicProxyRouter.AddProxyRouteToRuntime(preparedEndpoint)
proxyEndpointCreated = &thisProxyEndpoint
} else if eptype == "root" {
//Get the default site options and target
dsOptString, err := utils.PostPara(r, "defaultSiteOpt")
if err != nil {
utils.SendErrorResponse(w, "default site action not defined")
return
}
var defaultSiteOption int = 1
opt, err := strconv.Atoi(dsOptString)
if err != nil {
utils.SendErrorResponse(w, "invalid default site option")
return
}
defaultSiteOption = opt
dsVal, err := utils.PostPara(r, "defaultSiteVal")
if err != nil && (defaultSiteOption == 1 || defaultSiteOption == 2) {
//Reverse proxy or redirect, must require value to be set
utils.SendErrorResponse(w, "target not defined")
return
}
//Write the root options to file
rootRoutingEndpoint := dynamicproxy.ProxyEndpoint{
ProxyType: dynamicproxy.ProxyType_Root,
RootOrMatchingDomain: "/",
Domain: endpoint,
RequireTLS: useTLS,
BypassGlobalTLS: useBypassGlobalTLS,
SkipCertValidations: skipTlsValidation,
RequireBasicAuth: requireBasicAuth,
BasicAuthCredentials: basicAuthCredentials,
BypassGlobalTLS: false,
SkipCertValidations: false,
DefaultSiteOption: defaultSiteOption,
DefaultSiteValue: dsVal,
}
dynamicProxyRouter.AddSubdomainRoutingService(&thisOption)
} else if eptype == "root" {
rootname = "root"
thisOption := dynamicproxy.RootOptions{
ProxyLocation: endpoint,
RequireTLS: useTLS,
preparedRootProxyRoute, err := dynamicProxyRouter.PrepareProxyRoute(&rootRoutingEndpoint)
if err != nil {
utils.SendErrorResponse(w, "unable to prepare root routing: "+err.Error())
return
}
dynamicProxyRouter.SetRootProxy(&thisOption)
dynamicProxyRouter.SetProxyRouteAsRoot(preparedRootProxyRoute)
proxyEndpointCreated = &rootRoutingEndpoint
} else {
//Invalid eptype
utils.SendErrorResponse(w, "Invalid endpoint type")
utils.SendErrorResponse(w, "invalid endpoint type")
return
}
//Save it
thisProxyConfigRecord := Record{
ProxyType: eptype,
Rootname: rootname,
ProxyTarget: endpoint,
UseTLS: useTLS,
BypassGlobalTLS: useBypassGlobalTLS,
SkipTlsValidation: skipTlsValidation,
RequireBasicAuth: requireBasicAuth,
BasicAuthCredentials: basicAuthCredentials,
//Save the config to file
err = SaveReverseProxyConfig(proxyEndpointCreated)
if err != nil {
SystemWideLogger.PrintAndLog("Proxy", "Unable to save new proxy rule to file", err)
return
}
SaveReverseProxyConfigToFile(&thisProxyConfigRecord)
//Update utm if exists
if uptimeMonitor != nil {
@ -320,17 +336,11 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
/*
ReverseProxyHandleEditEndpoint handles proxy endpoint edit
This endpoint do not handle
basic auth credential update. The credential
will be loaded from old config and reused
(host only, for root use Default Site page to 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")
@ -371,50 +381,31 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
requireBasicAuth := (rba == "true")
//Load the previous basic auth credentials from current proxy rules
targetProxyEntry, err := dynamicProxyRouter.LoadProxy(eptype, rootNameOrMatchingDomain)
targetProxyEntry, err := dynamicProxyRouter.LoadProxy(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,
BypassGlobalTLS: false,
SkipCertValidations: skipTlsValidation,
RequireBasicAuth: requireBasicAuth,
BasicAuthCredentials: targetProxyEntry.BasicAuthCredentials,
}
targetProxyEntry.Remove()
dynamicProxyRouter.AddVirtualDirectoryProxyService(&thisOption)
//Generate a new proxyEndpoint from the new config
newProxyEndpoint := dynamicproxy.CopyEndpoint(targetProxyEntry)
newProxyEndpoint.Domain = endpoint
newProxyEndpoint.RequireTLS = useTLS
newProxyEndpoint.BypassGlobalTLS = bypassGlobalTLS
newProxyEndpoint.SkipCertValidations = skipTlsValidation
newProxyEndpoint.RequireBasicAuth = requireBasicAuth
} else if eptype == "subd" {
thisOption := dynamicproxy.SubdOptions{
MatchingDomain: targetProxyEntry.RootOrMatchingDomain,
Domain: endpoint,
RequireTLS: useTLS,
BypassGlobalTLS: bypassGlobalTLS,
SkipCertValidations: skipTlsValidation,
RequireBasicAuth: requireBasicAuth,
BasicAuthCredentials: targetProxyEntry.BasicAuthCredentials,
}
targetProxyEntry.Remove()
dynamicProxyRouter.AddSubdomainRoutingService(&thisOption)
//Prepare to replace the current routing rule
readyRoutingRule, err := dynamicProxyRouter.PrepareProxyRoute(newProxyEndpoint)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
targetProxyEntry.Remove()
dynamicProxyRouter.AddProxyRouteToRuntime(readyRoutingRule)
//Save it to file
thisProxyConfigRecord := Record{
ProxyType: eptype,
Rootname: targetProxyEntry.RootOrMatchingDomain,
ProxyTarget: endpoint,
UseTLS: useTLS,
SkipTlsValidation: skipTlsValidation,
RequireBasicAuth: requireBasicAuth,
BasicAuthCredentials: targetProxyEntry.BasicAuthCredentials,
}
SaveReverseProxyConfigToFile(&thisProxyConfigRecord)
SaveReverseProxyConfig(newProxyEndpoint)
//Update uptime monitor
UpdateUptimeMonitorTargets()
@ -429,21 +420,20 @@ func DeleteProxyEndpoint(w http.ResponseWriter, r *http.Request) {
return
}
ptype, err := utils.PostPara(r, "ptype")
if err != nil {
utils.SendErrorResponse(w, "Invalid ptype given")
return
}
//Remove the config from runtime
err = dynamicProxyRouter.RemoveProxyEndpointByRootname(ptype, ep)
err = dynamicProxyRouter.RemoveProxyEndpointByRootname(ep)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
//Remove the config from file
RemoveReverseProxyConfigFile(ep)
fmt.Println(ep)
err = RemoveReverseProxyConfig(ep)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
//Update utm if exists
if uptimeMonitor != nil {
@ -473,14 +463,8 @@ func UpdateProxyBasicAuthCredentials(w http.ResponseWriter, r *http.Request) {
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)
targetProxy, err := dynamicProxyRouter.LoadProxy(ep)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
@ -502,17 +486,6 @@ func UpdateProxyBasicAuthCredentials(w http.ResponseWriter, r *http.Request) {
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")
@ -520,7 +493,7 @@ func UpdateProxyBasicAuthCredentials(w http.ResponseWriter, r *http.Request) {
}
//Load the target proxy object from router
targetProxy, err := dynamicProxyRouter.LoadProxy(ptype, ep)
targetProxy, err := dynamicProxyRouter.LoadProxy(ep)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
@ -570,7 +543,7 @@ func UpdateProxyBasicAuthCredentials(w http.ResponseWriter, r *http.Request) {
targetProxy.BasicAuthCredentials = mergedCredentials
//Save it to file
SaveReverseProxyEndpointToFile(targetProxy)
SaveReverseProxyConfig(targetProxy)
//Replace runtime configuration
targetProxy.UpdateToRuntime()
@ -593,14 +566,8 @@ func ListProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request) {
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)
targetProxy, err := dynamicProxyRouter.LoadProxy(ep)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
@ -624,12 +591,6 @@ func AddProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request) {
return
}
ptype, err := utils.PostPara(r, "ptype")
if err != nil {
utils.SendErrorResponse(w, "Invalid ptype given")
return
}
matchingPrefix, err := utils.PostPara(r, "prefix")
if err != nil {
utils.SendErrorResponse(w, "Invalid matching prefix given")
@ -637,7 +598,7 @@ func AddProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request) {
}
//Load the target proxy object from router
targetProxy, err := dynamicProxyRouter.LoadProxy(ptype, ep)
targetProxy, err := dynamicProxyRouter.LoadProxy(ep)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
@ -666,7 +627,7 @@ func AddProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request) {
//Save configs to runtime and file
targetProxy.UpdateToRuntime()
SaveReverseProxyEndpointToFile(targetProxy)
SaveReverseProxyConfig(targetProxy)
utils.SendOK(w)
}
@ -679,12 +640,6 @@ func RemoveProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request)
return
}
ptype, err := utils.PostPara(r, "ptype")
if err != nil {
utils.SendErrorResponse(w, "Invalid ptype given")
return
}
matchingPrefix, err := utils.PostPara(r, "prefix")
if err != nil {
utils.SendErrorResponse(w, "Invalid matching prefix given")
@ -692,7 +647,7 @@ func RemoveProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request)
}
// Load the target proxy object from router
targetProxy, err := dynamicProxyRouter.LoadProxy(ptype, ep)
targetProxy, err := dynamicProxyRouter.LoadProxy(ep)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
@ -717,7 +672,7 @@ func RemoveProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request)
// Save configs to runtime and file
targetProxy.UpdateToRuntime()
SaveReverseProxyEndpointToFile(targetProxy)
SaveReverseProxyConfig(targetProxy)
utils.SendOK(w)
}
@ -728,16 +683,28 @@ func ReverseProxyStatus(w http.ResponseWriter, r *http.Request) {
}
func ReverseProxyList(w http.ResponseWriter, r *http.Request) {
eptype, err := utils.PostPara(r, "type") //Support root, vdir and subd
eptype, err := utils.PostPara(r, "type") //Support root and host
if err != nil {
utils.SendErrorResponse(w, "type not defined")
return
}
if eptype == "vdir" {
if eptype == "host" {
results := []*dynamicproxy.ProxyEndpoint{}
dynamicProxyRouter.ProxyEndpoints.Range(func(key, value interface{}) bool {
results = append(results, value.(*dynamicproxy.ProxyEndpoint))
thisEndpoint := dynamicproxy.CopyEndpoint(value.(*dynamicproxy.ProxyEndpoint))
//Clear the auth passwords before showing to front-end
cleanedCredentials := []*dynamicproxy.BasicAuthCredentials{}
for _, user := range thisEndpoint.BasicAuthCredentials {
cleanedCredentials = append(cleanedCredentials, &dynamicproxy.BasicAuthCredentials{
Username: user.Username,
PasswordHash: "",
})
}
thisEndpoint.BasicAuthCredentials = cleanedCredentials
results = append(results, thisEndpoint)
return true
})
@ -745,19 +712,6 @@ func ReverseProxyList(w http.ResponseWriter, r *http.Request) {
return results[i].Domain < results[j].Domain
})
js, _ := json.Marshal(results)
utils.SendJSONResponse(w, string(js))
} else if eptype == "subd" {
results := []*dynamicproxy.ProxyEndpoint{}
dynamicProxyRouter.SubdomainEndpoint.Range(func(key, value interface{}) bool {
results = append(results, value.(*dynamicproxy.ProxyEndpoint))
return true
})
sort.Slice(results, func(i, j int) bool {
return results[i].RootOrMatchingDomain < results[j].RootOrMatchingDomain
})
js, _ := json.Marshal(results)
utils.SendJSONResponse(w, string(js))
} else if eptype == "root" {
@ -881,34 +835,3 @@ func HandleIncomingPortSet(w http.ResponseWriter, r *http.Request) {
utils.SendOK(w)
}
// Handle list of root route options
func HandleRootRouteOptionList(w http.ResponseWriter, r *http.Request) {
js, _ := json.Marshal(dynamicProxyRouter.RootRoutingOptions)
utils.SendJSONResponse(w, string(js))
}
// Handle update of the root route edge case options. See dynamicproxy/rootRoute.go
func HandleRootRouteOptionsUpdate(w http.ResponseWriter, r *http.Request) {
enableUnsetSubdomainRedirect, err := utils.PostBool(r, "unsetRedirect")
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
unsetRedirectTarget, _ := utils.PostPara(r, "unsetRedirectTarget")
newRootOption := dynamicproxy.RootRoutingOptions{
EnableRedirectForUnsetRules: enableUnsetSubdomainRedirect,
UnsetRuleRedirectTarget: unsetRedirectTarget,
}
dynamicProxyRouter.RootRoutingOptions = &newRootOption
err = newRootOption.SaveToFile()
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
utils.SendOK(w)
}

View File

@ -101,6 +101,18 @@ func startupSequence() {
} else {
panic(err)
}
//Start the static web server
staticWebServer = webserv.NewWebServer(&webserv.WebServerOptions{
Sysdb: sysdb,
Port: "5487", //Default Port
WebRoot: *staticWebServerRoot,
EnableDirectoryListing: true,
EnableWebDirManager: *allowWebFileManager,
})
//Restore the web server to previous shutdown state
staticWebServer.RestorePreviousState()
//Create a netstat buffer
netstatBuffers, err = netstat.NewNetStatBuffer(300)
if err != nil {
@ -220,21 +232,6 @@ func startupSequence() {
log.Fatal(err)
}
/*
Static Web Server
Start the static web server
*/
staticWebServer = webserv.NewWebServer(&webserv.WebServerOptions{
Sysdb: sysdb,
Port: "5487", //Default Port
WebRoot: *staticWebServerRoot,
EnableDirectoryListing: true,
EnableWebDirManager: *allowWebFileManager,
})
//Restore the web server to previous shutdown state
staticWebServer.RestorePreviousState()
}
// This sequence start after everything is initialized

View File

@ -1,92 +1,119 @@
<div class="standardContainer">
<div class="ui basic segment">
<h2>Set Proxy Root</h2>
<p>The default routing point for all incoming traffics. For all routing not found in the proxy rules, request will be redirected to the proxy root server.</p>
<h2>Default Site</h2>
<p>Default routing options for inbound traffic (previously called Proxy Root)</p>
<div class="ui form">
<div class="field">
<label>Proxy Root</label>
<input type="text" id="proxyRoot" onchange="checkRootRequireTLS(this.value);">
<small>E.g. localhost:8080</small>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" id="rootReqTLS">
<label>Root require TLS connection <br><small>Check this if your proxy root URL starts with https://</small></label>
<div class="grouped fields">
<label>What to show when Zoraxy is hit with an unknown Host?</label>
<div class="field">
<div class="ui radio defaultsite checkbox">
<input type="radio" name="defaultsiteOption" checked="checked" value="webserver">
<label>Internal Static Web Server<br>
<small>Check this if you prefer a more Apache / Nginx like experience</small>
</label>
</div>
</div>
<div class="field">
<div class="ui radio defaultsite checkbox">
<input type="radio" name="defaultsiteOption" value="proxy">
<label>Reverse Proxy Target<br>
<small>Proxy the request to a target IP / domain</small>
</label>
</div>
</div>
<div class="field">
<div class="ui radio defaultsite checkbox">
<input type="radio" name="defaultsiteOption" value="redirect">
<label>Redirect<br>
<small>Redirect the user to a new location</small>
</label>
</div>
</div>
<div class="field">
<div class="ui radio defaultsite checkbox">
<input type="radio" name="defaultsiteOption" value="notfound">
<label>Show 404 NOT FOUND<br>
<small>Respond to request with a 404 page</small>
</label>
</div>
</div>
</div>
<div class="ui horizontal divider">OR</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" id="useStaticWebServer" onchange="handleUseStaticWebServerAsRoot()">
<label>Use Static Web Server as Root <br><small>Check this if you prefer a more Apache Web Server like experience</small></label>
</div>
</div>
<br>
<button class="ui basic button" onclick="setProxyRoot()"><i class="teal home icon" ></i> Update Proxy Root</button>
<div class="ui divider"></div>
<div class="field">
<h4>Root Routing Options</h4>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" id="unsetRedirect">
<label>Enable redirect for unset subdomains <br><small>Redirect subdomain that is not found to custom domain</small></label>
</div>
</div>
<div class="ui basic segment" id="unsetRedirectDomainWrapper" style="background-color: #f7f7f7; border-radius: 1em; margin-left: 2em; padding-left: 2em; display:none;">
<div style="
position: absolute;
top:0;
left: 1em;
width: 0px;
height: 0px;
margin-top: -10px;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-bottom: 10px solid #f7f7f7;">
</div>
<!-- Reverse Proxy as Default Site Options -->
<div id="defaultSiteProxyOptions" class="ui basic segment advanceoptions defaultSiteOptionDetails" style="display:none; ">
<div class="ui form">
<div class="field">
<label>Reverse Proxy Target</label>
<input type="text" id="proxyRoot" onchange="checkRootRequireTLS(this.value);">
<small>e.g. localhost:8080 / 192.168.0.100:80 / example.com</small>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" id="rootReqTLS">
<label>Reverse proxy target require TLS connection <br><small>Check this if your proxy target URL require connection with https://</small></label>
</div>
</div>
</div>
</div>
<!-- Redirect as default site Options-->
<div id="defaultSiteRedirectOptions" class="ui basic segment advanceoptions defaultSiteOptionDetails" style="display:none;"">
<div class="ui form">
<div class="field">
<label>Redirect target domain</label>
<div class="ui input">
<input id="unsetRedirectDomain" type="text" placeholder="http://example.com">
<input id="redirectDomain" type="text" placeholder="http://example.com">
</div>
<small>Unset subdomain will be redirected to the link above. Remember to include the protocol (e.g. http:// or https://)<br>
Leave empty for redirecting to upper level domain (e.g. notfound.example.com <i class="right arrow icon"></i> example.com)</small>
<small>Unset subdomain will be redirected to the link above. Remember to include the protocol (e.g. http:// or https://)</small>
</div>
</div>
<br>
<button class="ui basic button" onclick="updateRootOptions()"><i class="blue save icon" ></i> Save Root Options</button>
</div>
<button class="ui basic button" onclick="setProxyRoot(this)"><i class="green checkmark icon" ></i> Apply Changes</button>
<button class="ui basic button" onclick="initRootInfo()"><i class="refresh icon" ></i> Reset</button>
<br>
</div>
</div>
<script>
var currentDefaultSiteOption = 0; //For enum see typedef.go
$("#advanceRootSettings").accordion();
function handleUseStaticWebServerAsRoot(){
let useStaticWebServer = $("#useStaticWebServer")[0].checked;
if (useStaticWebServer){
//Handle toggle events of option radio boxes
function updateAvaibleDefaultSiteOptions(){
let selectedDefaultSite = $('input[name="defaultsiteOption"]:checked').val();
$(".defaultSiteOptionDetails").hide();
if (selectedDefaultSite == "webserver"){
//Use build in web server as target
let staticWebServerURL = "127.0.0.1:" + $("#webserv_listenPort").val();
$("#proxyRoot").val(staticWebServerURL);
$("#proxyRoot").parent().addClass("disabled");
$("#rootReqTLS").parent().checkbox("set unchecked");
$("#rootReqTLS").parent().addClass("disabled");
//Check if web server is enabled. If not, ask if the user want to enable it
/*if (!$("#webserv_enable").parent().checkbox("is checked")){
confirmBox("Enable static web server now?", function(choice){
if (choice == true){
$("#webserv_enable").parent().checkbox("set checked");
}
});
}*/
}else{
currentDefaultSiteOption = 0;
}else if (selectedDefaultSite == "proxy"){
$("#defaultSiteProxyOptions").show();
$("#rootReqTLS").parent().removeClass("disabled");
$("#proxyRoot").parent().removeClass("disabled");
initRootInfo();
currentDefaultSiteOption = 1;
}else if (selectedDefaultSite == "redirect"){
$("#defaultSiteRedirectOptions").show();
currentDefaultSiteOption = 2;
}else if (selectedDefaultSite == "notfound"){
currentDefaultSiteOption = 3;
}else{
//Unknown option
return;
}
}
//Bind events to the radio boxes
function bindDefaultSiteRadioCheckboxEvents(){
$('input[type=radio][name=defaultsiteOption]').off("change").on("change", function() {
updateAvaibleDefaultSiteOptions();
});
}
function initRootInfo(callback=undefined){
@ -94,6 +121,22 @@
if (data == null){
}else{
var $radios = $('input:radio[name=defaultsiteOption]');
let proxyType = data.DefaultSiteOption;
//See typedef.go for enum conversion
if (proxyType == 0){
$radios.filter('[value=webserver]').prop('checked', true);
}else if (proxyType == 1){
$radios.filter('[value=proxy]').prop('checked', true);
$("#proxyRoot").val(data.DefaultSiteValue);
}else if (proxyType == 2){
$radios.filter('[value=redirect]').prop('checked', true);
$("#redirectDomain").val(data.DefaultSiteValue);
}else if (proxyType == 3){
$radios.filter('[value=notfound]').prop('checked', true);
}
updateAvaibleDefaultSiteOptions();
$("#proxyRoot").val(data.Domain);
checkRootRequireTLS(data.Domain);
}
@ -104,21 +147,9 @@
});
}
initRootInfo(function(){
updateWebServerLinkSettings();
bindDefaultSiteRadioCheckboxEvents();
});
//Update the current web server port settings
function updateWebServerLinkSettings(){
isUsingStaticWebServerAsRoot(function(isUsingWebServ){
if (isUsingWebServ){
$(".webservRootDisabled").addClass("disabled");
$("#useStaticWebServer").parent().checkbox("set checked");
}else{
$(".webservRootDisabled").removeClass("disabled");
$("#useStaticWebServer").parent().checkbox("set unchecked");
}
})
}
function isUsingStaticWebServerAsRoot(callback){
let currentProxyRoot = $("#proxyRoot").val().trim();
@ -131,47 +162,12 @@
}
function updateRootSettingStates(){
$.get("/api/cert/tls", function(data){
if (data == true){
$("#disableRootTLS").parent().removeClass('disabled').attr("title", "");
}else{
$("#disableRootTLS").parent().addClass('disabled').attr("title", "TLS listener is not enabled");
}
});
}
//Bind event to tab switch
tabSwitchEventBind["setroot"] = function(){
//On switch over to this page, update root info
updateRootSettingStates();
}
//Toggle the display status of the input box for domain setting
function updateRedirectionDomainSettingInputBox(useRedirect){
if(useRedirect){
$("#unsetRedirectDomainWrapper").stop().finish().slideDown("fast");
}else{
$("#unsetRedirectDomainWrapper").stop().finish().slideUp("fast");
}
}
function checkCustomRedirectForUnsetSubd(){
$.get("/api/proxy/root/listOptions", function(data){
$("#unsetRedirect")[0].checked = data.EnableRedirectForUnsetRules || false;
$("#unsetRedirectDomain").val(data.UnsetRuleRedirectTarget);
updateRedirectionDomainSettingInputBox(data.EnableRedirectForUnsetRules);
//Bind event to the checkbox
$("#unsetRedirect").off("change").on("change", function(){
let useRedirect = $("#unsetRedirect")[0].checked;
updateRedirectionDomainSettingInputBox(useRedirect);
});
});
}
checkCustomRedirectForUnsetSubd();
//Check if the given domain will redirect to https
function checkRootRequireTLS(targetDomain){
//Trim off the http or https from the origin
@ -193,14 +189,15 @@
}else if (data == "http"){
$("#rootReqTLS").parent().checkbox("set unchecked");
}
}
})
}
//Set the new proxy root option
function setProxyRoot(){
function setProxyRoot(btn=undefined){
if (btn != undefined){
$(btn).addClass("disabled");
}
var newpr = $("#proxyRoot").val();
if (newpr.trim() == ""){
$("#proxyRoot").parent().addClass('error');
@ -211,10 +208,36 @@
var rootReqTls = $("#rootReqTLS")[0].checked;
//proxy mode or redirect mode, check for input values
var defaultSiteValue = "";
if (currentDefaultSiteOption == 1){
if ($("#proxyRoot").val().trim() == ""){
$("#proxyRoot").parent().addClass("error");
return;
}
defaultSiteValue = $("#proxyRoot").val().trim();
$("#proxyRoot").parent().removeClass("error");
}else if (currentDefaultSiteOption == 2){
if ($("#redirectDomain").val().trim() == ""){
$("#redirectDomain").parent().addClass("error");
return;
}
defaultSiteValue = $("#redirectDomain").val().trim();
$("#redirectDomain").parent().removeClass("error");
}
//Create the endpoint by calling add
$.ajax({
url: "/api/proxy/add",
data: {"type": "root", tls: rootReqTls, ep: newpr},
data: {
"type": "root",
"tls": rootReqTls,
"ep": newpr,
"defaultSiteOpt": currentDefaultSiteOption,
"defaultSiteVal":defaultSiteValue,
},
method: "POST",
success: function(data){
if (data.error != undefined){
msgbox(data.error, false, 5000);
@ -231,37 +254,20 @@
setTimeout(function(){
//Update the checkbox
updateWebServerLinkSettings();
msgbox("Proxy Root Updated");
}, 1000);
}, 100);
})
});
}
if (btn != undefined){
$(btn).removeClass("disabled");
}
}
});
}
function updateRootOptions(){
$.ajax({
type: "POST",
url: "/api/proxy/root/updateOptions",
data: {
unsetRedirect: $("#unsetRedirect")[0].checked,
unsetRedirectTarget: $("#unsetRedirectDomain").val().trim(),
},
success: function(data) {
if (data.error != undefined){
msgbox(data.error, false);
}else{
msgbox("Root Routing Options updated");
}
},
error: function(error) {
console.log("Error:", error);
}
});
}
</script>

View File

@ -1,119 +1,114 @@
<div class="ui stackable grid">
<!-- Proxy Create Form-->
<style>
.rulesInstructions{
background: var(--theme_background) !important;
color: var(--theme_lgrey);
border-radius: 1em !important;
}
</style>
<div class="standardContainer">
<div class="ui stackable grid">
<div class="ten wide column">
<div class="standardContainer">
<div class="ui basic segment" style="margin-top: 1em;">
<h2>New Proxy Rule</h2>
<p>You can create a proxy endpoing by subdomain or virtual directories</p>
<div class="ui form">
<div class="field">
<label>Proxy Type</label>
<div class="ui selection dropdown">
<input type="hidden" id="ptype" value="subd" onchange="handleProxyTypeOptionChange(this.value)">
<i class="dropdown icon"></i>
<div class="default text">Proxy Type</div>
<div class="menu">
<div class="item" data-value="subd">Sub-domain</div>
<div class="item" data-value="vdir">Virtual Directory</div>
</div>
</div>
</div>
<div class="field">
<label>Subdomain Matching Keyword / Virtual Directory Name</label>
<input type="text" id="rootname" placeholder="s1.mydomain.com">
</div>
<div class="field">
<label>Target IP Address or Domain Name with port</label>
<input type="text" id="proxyDomain" onchange="autoCheckTls(this.value);">
<small>E.g. 192.168.0.101:8000 or example.com</small>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" id="reqTls">
<label>Proxy Target require TLS Connection <br><small>(i.e. Your proxy target starts with https://)</small></label>
</div>
</div>
<!-- Advance configs -->
<div class="ui basic segment" style="background-color: #f7f7f7; border-radius: 1em;">
<div id="advanceProxyRules" class="ui fluid accordion">
<div class="title">
<i class="dropdown icon"></i>
Advance Settings
</div>
<div class="content">
<p></p>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" id="skipTLSValidation">
<label>Ignore TLS/SSL Verification Error<br><small>For targets that is using self-signed, expired certificate (Not Recommended)</small></label>
</div>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" id="bypassGlobalTLS">
<label>Allow plain HTTP access<br><small>Allow this subdomain to be connected without TLS (Require HTTP server enabled on port 80)</small></label>
</div>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" id="requireBasicAuth">
<label>Require Basic Auth<br><small>Require client to login in order to view the page</small></label>
</div>
</div>
<div id="basicAuthCredentials" class="field">
<p>Enter the username and password for allowing them to access this proxy endpoint</p>
<table class="ui very basic celled table">
<thead>
<tr>
<th>Username</th>
<th>Password</th>
<th>Remove</th>
</tr></thead>
<tbody id="basicAuthCredentialTable">
<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="basicAuthCredUsername" type="text" placeholder="Username" autocomplete="off">
</div>
<div class="field">
<input id="basicAuthCredPassword" type="password" placeholder="Password" autocomplete="off">
</div>
<div class="field">
<button class="ui basic button" onclick="addCredentials();"><i class="blue add icon"></i> Add Credential</button>
</div>
</div>
</div>
</div>
</div>
</div>
<br>
<button class="ui basic button" onclick="newProxyEndpoint();"><i class="blue add icon"></i> Create Endpoint</button>
<br><br>
<div class="ui basic segment" style="border-radius: 1em; padding: 1em !important;">
<h2>New Proxy Rule</h2>
<p>You can add more proxy rules to support more site via domain / subdomains</p>
<div class="ui form">
<div class="field">
<label>Matching Keyword / Domain</label>
<input type="text" id="rootname" placeholder="mydomain.com">
<small>Support subdomain and wildcard, e.g. s1.mydomain.com or *.test.mydomain.com</small>
</div>
<div class="field">
<label>Target IP Address or Domain Name with port</label>
<input type="text" id="proxyDomain" onchange="autoCheckTls(this.value);">
<small>E.g. 192.168.0.101:8000 or example.com</small>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" id="reqTls">
<label>Proxy Target require TLS Connection <br><small>(i.e. Your proxy target starts with https://)</small></label>
</div>
</div>
<!-- Advance configs -->
<div class="ui basic segment" style="background-color: #f7f7f7; border-radius: 1em;">
<div id="advanceProxyRules" class="ui fluid accordion">
<div class="title">
<i class="dropdown icon"></i>
Advance Settings
</div>
<div class="content">
<p></p>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" id="skipTLSValidation">
<label>Ignore TLS/SSL Verification Error<br><small>For targets that is using self-signed, expired certificate (Not Recommended)</small></label>
</div>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" id="bypassGlobalTLS">
<label>Allow plain HTTP access<br><small>Allow this subdomain to be connected without TLS (Require HTTP server enabled on port 80)</small></label>
</div>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" id="requireBasicAuth">
<label>Require Basic Auth<br><small>Require client to login in order to view the page</small></label>
</div>
</div>
<div id="basicAuthCredentials" class="field">
<p>Enter the username and password for allowing them to access this proxy endpoint</p>
<table class="ui very basic celled table">
<thead>
<tr>
<th>Username</th>
<th>Password</th>
<th>Remove</th>
</tr></thead>
<tbody id="basicAuthCredentialTable">
<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="basicAuthCredUsername" type="text" placeholder="Username" autocomplete="off">
</div>
<div class="field">
<input id="basicAuthCredPassword" type="password" placeholder="Password" autocomplete="off">
</div>
<div class="field">
<button class="ui basic button" onclick="addCredentials();"><i class="blue add icon"></i> Add Credential</button>
</div>
</div>
</div>
</div>
</div>
</div>
<br>
<button class="ui basic button" onclick="newProxyEndpoint();"><i class="blue add icon"></i> Create Endpoint</button>
<br><br>
</div>
</div>
</div>
<div class="six wide column">
<div class="ui basic segment" style="height: 100%; background-color: var(--theme_grey); color: var(--theme_lgrey);">
<br>
<span style="font-size: 1.2em; font-weight: 300;">Subdomain</span><br>
Example of subdomain matching keyword:<br>
<code>s1.arozos.com</code> <br>(Any access starting with s1.arozos.com will be proxy to the IP address below)<br>
<div class="ui basic segment rulesInstructions">
<span style="font-size: 1.2em; font-weight: 300;"><i class="ui yellow star icon"></i> Domain</span><br>
Example of domain matching keyword:<br>
<code>arozos.com</code> <br>Any acess requesting arozos.com will be proxy to the IP address below<br>
<div class="ui divider"></div>
<span style="font-size: 1.2em; font-weight: 300;">Virtual Directory</span><br>
Example of virtual directory name: <br>
<code>/s1/home/</code> <br>(Any access to {this_server}/s1/home/ will be proxy to the IP address below)<br>
You can also ignore the tailing slash for wildcard like usage.<br>
<code>/s1/room-</code> <br>Any access to {this_server}/s1/classroom_* will be proxied, for example: <br>
<span style="font-size: 1.2em; font-weight: 300;"><i class="ui yellow star icon"></i> Subdomain</span><br>
Example of subdomain matching keyword:<br>
<code>s1.arozos.com</code> <br>Any request starting with s1.arozos.com will be proxy to the IP address below<br>
<div class="ui divider"></div>
<span style="font-size: 1.2em; font-weight: 300;"><i class="ui yellow star icon"></i> Wildcard</span><br>
Example of wildcard matching keyword:<br>
<code>*.arozos.com</code> <br>Any request with a host name matching *.arozos.com will be proxy to the IP address below. Here are some examples.<br>
<div class="ui list">
<div class="item"><code>/s1/room-101</code></div>
<div class="item"><code>/s1/room-102/</code></div>
<div class="item"><code>/s1/room-103/map.txt</code></div>
</div><br>
<div class="item"><code>www.arozos.com</code></div>
<div class="item"><code>foo.bar.arozos.com</code></div>
</div>
<br>
</div>
</div>
@ -124,7 +119,6 @@
//New Proxy Endpoint
function newProxyEndpoint(){
var type = $("#ptype").val();
var rootname = $("#rootname").val();
var proxyDomain = $("#proxyDomain").val();
var useTLS = $("#reqTls")[0].checked;
@ -132,20 +126,6 @@
var bypassGlobalTLS = $("#bypassGlobalTLS")[0].checked;
var requireBasicAuth = $("#requireBasicAuth")[0].checked;
if (type === "vdir") {
if (!rootname.startsWith("/")) {
rootname = "/" + rootname
$("#rootname").val(rootname);
}
}else{
if (!isSubdomainDomain(rootname)){
//This doesn't seems like a subdomain
if (!confirm(rootname + " does not looks like a subdomain. Continue anyway?")){
return;
}
}
}
if (rootname.trim() == ""){
$("#rootname").parent().addClass("error");
return
@ -164,7 +144,7 @@
$.ajax({
url: "/api/proxy/add",
data: {
type: type,
type: "host",
rootname: rootname,
tls: useTLS,
ep: proxyDomain,
@ -178,8 +158,6 @@
msgbox(data.error, false, 5000);
}else{
//OK
listVdirs();
listSubd();
//Clear old data
@ -189,7 +167,7 @@
updateTable();
//Check if it is a new subdomain and TLS enabled
if (type == "subd" && $("#tls").checkbox("is checked")){
if ($("#tls").checkbox("is checked")){
confirmBox("Request new SSL Cert for this subdomain?", function(choice){
if (choice == true){
//Load the prefer CA from TLS page
@ -214,23 +192,14 @@
}
function handleProxyTypeOptionChange(newType){
if (newType == "subd"){
$("#bypassGlobalTLS").parent().removeClass("disabled");
}else if (newType == "vdir"){
$("#bypassGlobalTLS").parent().addClass("disabled");
}
}
//Generic functions for delete rp endpoints
function deleteEndpoint(ptype, epoint){
if (confirm("Confirm remove proxy for :" + epoint + " (type: " + ptype + ")?")){
if (confirm("Confirm remove proxy for :" + epoint + "?")){
$.ajax({
url: "/api/proxy/del",
data: {ep: epoint, ptype: ptype},
data: {ep: epoint, },
success: function(){
listVdirs();
listSubd();
listProxyEndpoints();
}
})
}
@ -330,14 +299,6 @@
updateTable();
}
//Check if a string is a valid subdomain
function isSubdomainDomain(str) {
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);
}
/*
Inline editor for subd.html and vdir.html
*/
@ -411,7 +372,7 @@
<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 if (datatype == "inbound" && payload.ProxyType == 0){
}else if (datatype == "inbound"){
let originalContent = $(column).html();
column.empty().append(`${originalContent}
<div class="ui divider"></div>
@ -429,9 +390,8 @@
$("#" + endpointType).find(".editBtn").addClass("disabled");
}
function exitProxyInlineEdit(){
listSubd();
listVdirs();
function exitProxyInlineEdit(endpointType){
listProxyEndpoints();
$("#" + endpointType).find(".editBtn").removeClass("disabled");
}
@ -440,14 +400,8 @@
if (row.length == 0){
return;
}
var epttype = $(row).attr("class");
if (epttype == "subdEntry"){
epttype = "subd";
}else if (epttype == "vdirEntry"){
epttype = "vdir";
}
var epttype = "host";
let newDomain = $(row).find(".Domain").val();
let requireTLS = $(row).find(".RequireTLS")[0].checked;
let skipCertValidations = $(row).find(".SkipCertValidations")[0].checked;
@ -473,11 +427,7 @@
msgbox(data.error, false, 6000);
}else{
msgbox("Proxy endpoint updated");
if (epttype == "subd"){
listSubd();
}else if (epttype == "vdir"){
listVdirs();
}
listProxyEndpoints();
}
}
})

View File

@ -17,7 +17,7 @@
</div>
</div>
<div class="six wide column statisticWrapper">
<div class="ui greybackground statustab segment">
<div class="ui statustab segment">
<h5 class="ui header">
<i class="exchange icon"></i>
<div class="content">
@ -29,7 +29,7 @@
</div>
</h5>
<div class="ui divider"></div>
<h5 class="ui header">
<h5 class="ui header" style="margin-top: 0px;">
<i class="arrows alternate horizontal icon"></i>
<div class="content">
<span id="forwardtype"></span>
@ -39,7 +39,7 @@
</div>
</h5>
<div class="ui divider"></div>
<h5 class="ui header">
<h5 class="ui header" style="margin-top: 0px;">
<i class="map marker alternate icon"></i>
<div class="content">
<span id="country"></span>
@ -51,20 +51,27 @@
</div>
</div>
</div>
<div id="networkActWrapper" class="standardContainer" style="position: relative; margin-top: 1em;">
<div class="standardContainer" style="padding-bottom: 0 !important;">
<!-- Power Buttons-->
<button id="startbtn" class="ui basic button" onclick="startService();"><i class="ui green arrow alternate circle up icon"></i> Start Service</button>
<button id="stopbtn" class="ui basic notloopbackOnly disabled button" onclick="stopService();"><i class="ui red minus circle icon"></i> Stop Service</button>
<div class="ui divider"></div>
<h4>Network Status</h4>
<p>Overall Network I/O in Current Host Server</p>
</div>
<div id="networkActWrapper" class="standardContainer" style="position: relative;">
<canvas id="networkActivity"></canvas>
</div>
<div id="networkActivityPlaceHolder">
<p style="opacity: 0.5;"><i class="ui pause icon"></i> Graph Render Paused</p>
<p style="opacity: 0.5;"> Graph Render Paused</p>
</div>
<br>
<div class="standardContainer">
<h4>Basic Settings</h4>
<div class="ui divider"></div>
<h4>Global Settings</h4>
<p>Inbound Port (Port to be proxied)</p>
<div class="ui action fluid notloopbackOnly input">
<input type="text" id="incomingPort" placeholder="Incoming Port" value="80">
<button class="ui basic green notloopbackOnly button" onclick="handlePortChange();">Apply</button>
<button class="ui basic notloopbackOnly button" onclick="handlePortChange();"><i class="ui green checkmark icon"></i> Apply</button>
</div>
<br>
<div id="tls" class="ui toggle notloopbackOnly checkbox">
@ -99,11 +106,7 @@
</div>
</div>
</div>
<br><br>
<button id="startbtn" class="ui teal button" onclick="startService();">Start Service</button>
<button id="stopbtn" class="ui red notloopbackOnly disabled button" onclick="stopService();">Stop Service</button>
<div id="rploopbackWarning" class="ui segment" style="display:none;">
<b><i class="yellow warning icon"></i> Loopback Routing Warning</b><br>
<small>This management interface is a loopback proxied service. <br>If you want to shutdown the reverse proxy server, please remove the proxy rule for the management interface and refresh.</small>
@ -114,7 +117,7 @@
<div class="ui two column stackable grid">
<div class="column">
<p>Visitor Counts</p>
<table class="ui unstackable inverted celled table">
<table class="ui unstackable very basic celled table">
<thead>
<tr>
<th>Country ISO Code</th>
@ -130,7 +133,7 @@
</div>
<div class="column">
<p>Proxy Request Types</p>
<table class="ui unstackable inverted celled table">
<table class="ui unstackable very basic celled table">
<thead>
<tr>
<th>Proxy Type</th>
@ -175,13 +178,13 @@
}
$("#serverstatus").addClass("green");
$("#statusTitle").text("Online");
$("#rpStatusIcon").attr("class", "green circle check icon");
$("#rpStatusIcon").attr("class", "white circle check icon");
$("#statusText").text("Serving request on port: " + data.Option.Port);
}else{
$("#startbtn").removeClass("disabled");
$("#stopbtn").addClass("disabled");
$("#statusTitle").text("Offline");
$("#rpStatusIcon").attr("class", "black circle times icon")
$("#rpStatusIcon").attr("class", "yellow moon icon")
$("#statusText").text("Reverse proxy server is offline");
$("#serverstatus").removeClass("green");
}
@ -565,6 +568,7 @@
options: {
animation: false,
maintainAspectRatio: false,
bezierCurve: true,
tooltips: {enabled: false},
hover: {mode: null},
//stepped: 'middle',
@ -606,18 +610,18 @@
{
label: 'Inbound',
data: rxValues,
borderColor: "#4d9dd9",
borderWidth: 2,
backgroundColor: 'rgba(77, 157, 217, 0.2)',
borderColor: "#484bb8",
borderWidth: 1,
backgroundColor: 'rgba(72, 75, 184, 0.2)',
fill: true,
pointStyle: false,
},
{
label: 'Outbound',
data: txValues,
borderColor: '#ffe32b',
borderWidth: 2,
backgroundColor: 'rgba(255, 227, 43, 0.2)',
borderColor: '#02a9c1',
borderWidth: 1,
backgroundColor: 'rgba(2, 169, 193, 0.2)',
fill: true,
pointStyle: false,
}

View File

@ -1,14 +1,15 @@
<div class="standardContainer">
<div class="ui basic segment">
<h2>Subdomain</h2>
<p>Subdomains are a way to organize and identify different sections of a website or domain. They are essentially a prefix to the main domain name, separated by a dot. <br>For example, in the domain "blog.example.com," "blog" is the subdomain.</p>
<h2>HTTP Proxy</h2>
<p>Proxy HTTP server with HTTP or HTTPS. Sometime subdomain contain a prefix to the main domain name, separated by a dot. <br>For example, in the domain "blog.example.com," "blog" is the subdomain.</p>
</div>
<div style="width: 100%; overflow-x: auto; margin-bottom: 1em;">
<table class="ui celled sortable unstackable compact table">
<thead>
<tr>
<th>Matching Domain</th>
<th>Proxy To</th>
<th>Host</th>
<th>Destination</th>
<th>Virtual Directory</th>
<th>Basic Auth</th>
<th class="no-sort" style="min-width: 7.2em;">Actions</th>
</tr>
@ -18,13 +19,13 @@
</tbody>
</table>
</div>
<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="listProxyEndpoints();"><i class="green refresh icon"></i> Refresh</button>
<br><br>
</div>
<script>
function listSubd(){
$.get("/api/proxy/list?type=subd", function(data){
function listProxyEndpoints(){
$.get("/api/proxy/list?type=host", function(data){
$("#subdList").html(``);
if (data.error !== undefined){
$("#subdList").append(`<tr>
@ -32,7 +33,7 @@
</tr>`);
}else if (data.length == 0){
$("#subdList").append(`<tr>
<td data-label="" colspan="3"><i class="checkmark icon"></i> No Subdomain Proxy Record</td>
<td data-label="" colspan="3"><i class="green check circle icon"></i> No HTTP Proxy Record</td>
</tr>`);
}else{
data.forEach(subd => {
@ -45,9 +46,21 @@
}
}
let inboundTlsIcon = "";
if ($("#tls").checkbox("is checked")){
inboundTlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
if (subd.BypassGlobalTLS){
inboundTlsIcon = `<i class="grey lock icon" title="TLS Bypass Enabled"></i>`;
}
}else{
inboundTlsIcon = `<i class="yellow lock open icon" title="Plain Text Mode"></i>`;
}
$("#subdList").append(`<tr eptuuid="${subd.RootOrMatchingDomain}" payload="${subdData}" class="subdEntry">
<td data-label="" editable="true" datatype="inbound"><a href="//${subd.RootOrMatchingDomain}" target="_blank">${subd.RootOrMatchingDomain}</a></td>
<td data-label="" editable="true" datatype="inbound"><a href="//${subd.RootOrMatchingDomain}" target="_blank">${subd.RootOrMatchingDomain}</a> ${inboundTlsIcon}</td>
<td data-label="" editable="true" datatype="domain">${subd.Domain} ${tlsIcon}</td>
<td data-label="" editable="true" datatype="vdir">WIP</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" editable="true" datatype="action" data-label="">
<button class="ui circular mini basic icon button editBtn" onclick='editEndpoint("subd","${subd.RootOrMatchingDomain}")'><i class="edit icon"></i></button>
@ -61,6 +74,6 @@
//Bind on tab switch events
tabSwitchEventBind["subd"] = function(){
listSubd();
listProxyEndpoints();
}
</script>

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 B

View File

@ -36,18 +36,24 @@
<a class="item active" tag="status">
<i class="simplistic info circle icon"></i>Status
</a>
<a class="item" tag="setroot">
<i class="simplistic home icon"></i> Default Site
</a>
<div class="ui divider menudivider">Reverse Proxy</div>
<a class="item" tag="subd">
<i class="simplistic sitemap icon"></i> HTTP Proxy
</a>
<a class="item" tag="vdir">
<i class="simplistic folder icon"></i> Virtual Directory
</a>
<a class="item" tag="subd">
<i class="simplistic sitemap icon"></i> Subdomain Proxy
</a>
<a class="item" tag="rules">
<i class="simplistic plus square icon"></i> Create Proxy Rules
</a>
<a class="item" tag="setroot">
<i class="simplistic home icon"></i> Set Proxy Root
<a class="item" tag="tcpprox">
<i class="simplistic exchange icon"></i> TCP Proxy
</a>
<div class="ui divider menudivider">Access & Connections</div>
<a class="item" tag="cert">
<i class="simplistic lock icon"></i> TLS / SSL certificates
@ -65,16 +71,14 @@
<a class="item" tag="zgrok">
<i class="simplistic podcast icon"></i> Service Expose Proxy
</a>
<a class="item" tag="tcpprox">
<i class="simplistic exchange icon"></i> TCP Proxy
</a>
<div class="ui divider menudivider">Others</div>
<div class="ui divider menudivider">Hosting</div>
<a class="item" tag="webserv">
<i class="simplistic globe icon"></i> Static Web Server
</a>
<a class="item" tag="utm">
<i class="simplistic time icon"></i> Uptime Monitor
</a>
<div class="ui divider menudivider">Others</div>
<a class="item" tag="networktool">
<i class="simplistic terminal icon"></i> Network Tools
</a>
@ -235,7 +239,7 @@
$.get("/api/auth/logout", function(response) {
if (response === "OK") {
setTimeout(function(){
window.location.href = "/";
window.location.href = "/login.html";
}, 300);
}
});

View File

@ -7,48 +7,13 @@
<link rel="icon" type="image/png" href="./favicon.png" />
<title>Login | Zoraxy</title>
<link rel="stylesheet" href="script/semantic/semantic.min.css">
<link href="script/aos.css" rel="stylesheet">
<script src="script/aos.js"></script>
<script type="application/javascript" src="script/jquery-3.6.0.min.js"></script>
<script type="application/javascript" src="script/semantic/semantic.min.js"></script>
<style>
body {
background: rgb(245,245,245);
background: linear-gradient(28deg, rgba(245,245,245,1) 63%, rgba(255,255,255,1) 100%);
}
.background{
position: fixed;
top: 0;
right: 0;
height: 100%;
width: 100%;
opacity: 0.8;
z-index: -99;
background-image: url("img/public/bg.jpg");
background-size: auto 100%;
background-position: right top;
background-repeat: no-repeat;
overflow-x: hidden;
}
form {
margin:auto;
}
#loginForm{
height: 100%;
background-color: white;
width: 25em;
margin-left: 10em;
margin-top: 0 !important;
margin-bottom: 0 !important;
}
@media all and (max-width: 550px) {
/* CSS rules here for screens lower than 750px */
#loginForm{
width: calc(100% - 4em);
margin-left: 2em;
}
background: linear-gradient(60deg, rgba(84,58,183,1) 0%, rgba(0,172,193,1) 100%);
}
#errmsg{
@ -65,13 +30,97 @@
.ui.fluid.button.registerOnly{
display:none;
}
#loginForm {
border-radius: 1em;
width: 25em;
height: 450px;
position: absolute; /*Can also be `fixed`*/
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
/*Solves a problem in which the content is being cut when the div is smaller than its' wrapper:*/
max-width: 100%;
max-height: 100%;
overflow: auto;
background-color: white;
}
.wavebase {
position:fixed;
bottom: 0;
left: 0;
width: 100%;
height:5vh;
text-align:center;
padding-top: 1em;
background-color: white;
}
/*
Waves CSS
*/
#wavesWrapper{
position: fixed;
bottom: 5vh;
width: 100%;
left: 0;
}
.waves {
position:relative;
width: 100%;
height:15vh;
margin-bottom:-7px; /*Fix for safari gap*/
min-height:100px;
max-height:150px;
}
.parallax > use {
animation: move-forever 25s cubic-bezier(.55,.5,.45,.5) infinite;
}
.parallax > use:nth-child(1) {
animation-delay: -8s;
animation-duration: 28s;
}
.parallax > use:nth-child(2) {
animation-delay: -12s;
animation-duration: 40s;
}
.parallax > use:nth-child(3) {
animation-delay: -16s;
animation-duration: 52s;
}
.parallax > use:nth-child(4) {
animation-delay: -20s;
animation-duration: 80s;
}
@keyframes move-forever {
0% {
transform: translate3d(-90px,0,0);
}
100% {
transform: translate3d(85px,0,0);
}
}
/*Shrinking for mobile*/
@media (max-width: 768px) {
.waves {
height:40px;
min-height:40px;
}
}
</style>
</head>
<body>
<div class="background"></div>
<div id="loginForm" class="ui middle aligned center aligned grid">
<div class="column">
<form class="ui large form">
<div id="loginForm" class="ui middle aligned center aligned grid" data-aos="fade-up">
<div class="column" style="padding-top: 0 !important;">
<form class="ui large form content">
<div class="ui basic segment">
<img class="ui fluid image" src="img/public/logo.svg" style="pointer-events:none;">
<p class="registerOnly">Account Setup</p>
@ -99,19 +148,37 @@
<label>Remember Me</label>
</div>
</div>
<div id="loginbtn" class="ui fluid basic blue button loginOnly">Login</div>
<div id="regsiterbtn" class="ui fluid basic blue button registerOnly">Create</div>
<div id="loginbtn" class="ui fluid basic button loginOnly"> <i class="ui blue sign-in icon"></i> Login</div>
<div id="regsiterbtn" class="ui fluid basic button registerOnly"><i class="ui green checkmark icon"></i> Confirm</div>
<div id="errmsg"></div>
<div id="forgetPassword" class="field loginOnly" style="text-align: right;">
<div id="forgetPassword" class="field loginOnly" style="text-align: right; margin-top: 2em;">
<a href="#" onclick="sendResetAccountEmail();">Forget Password</a>
</div>
</div>
<div class="ui divider"></div>
<small>Proudly powered by Zoraxy</small>
</form>
</div>
</div>
<div id="wavesWrapper">
<!-- CSS waves-->
<svg class="waves" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 24 150 28" preserveAspectRatio="none" shape-rendering="auto">
<defs>
<path id="gentle-wave" d="M-160 44c30 0 58-18 88-18s 58 18 88 18 58-18 88-18 58 18 88 18 v44h-352z" />
</defs>
<g class="parallax">
<use xlink:href="#gentle-wave" x="48" y="0" fill="rgba(255,255,255,0.7" />
<use xlink:href="#gentle-wave" x="48" y="3" fill="rgba(255,255,255,0.5)" />
<use xlink:href="#gentle-wave" x="48" y="5" fill="rgba(255,255,255,0.3)" />
<use xlink:href="#gentle-wave" x="48" y="7" fill="#fff" />
</g>
</svg>
</div>
<div class="wavebase">
<p>Proudly powered by <a href="https://zoraxy.arozos.com" target="_blank">Zoraxy</a></p>
</div>
<script>
AOS.init();
var redirectionAddress = "/";
var loginAddress = "/api/auth/login";
$(".checkbox").checkbox();

View File

@ -7,6 +7,7 @@
--theme_green: #3c9c63;
--theme_fcolor: #979797;
--theme_advance: #f8f8f9;
--theme_background: linear-gradient(60deg, rgb(84, 58, 183) 0%, rgb(0, 172, 193) 100%);
}
body{
background-color:#f6f6f6;
@ -31,14 +32,14 @@ body{
padding: 0.4em;
padding-left: 1.2em;
padding-right: 1.2em;
background-color: #f5f5f5;
background-color: #ffffff;
margin-bottom: 1em;
border-bottom: 1px solid rgb(226, 226, 226);
position: fixed;
top: 0;
width: 100%;
z-index: 10;
box-shadow: 0px 1px 5px 0px rgba(38,38,38,0.26);
}
.menubar .logo{
@ -82,13 +83,21 @@ body{
}
.serverstatusWrapper{
padding-right: 0 !important;
padding-right: 0 !important;
}
.statisticWrapper{
margin-top: 1em;
padding-left: 0 !important;
padding-bottom: 0 !important;
padding-right: 1em !important;
}
.statisticWrapper .statustab{
margin-right: 1em;
}
/* Message Box */
#messageBox{
position: fixed;
@ -204,14 +213,6 @@ body{
@media screen and (min-width: 750px) {
#serverstatus{
border-top-left-radius: 1em !important;
}
.greybackground.statustab{
border-top-right-radius: 1em !important;
}
.standardContainer{
padding-left: 2.4em;
padding-right: 2.4em;
@ -221,7 +222,18 @@ body{
}
@media screen and (max-width: 750px) {
@media screen and (max-width: 748px) {
#serverstatus{
margin-left: 0 !important;
margin-right: 0 !important;
}
.statisticWrapper .statustab{
margin-left: 0 !important;
margin-right: 0 !important;
}
.toolbar {
position: fixed;
display: inline-block;
@ -257,19 +269,10 @@ body{
padding: 0em;
}
.ui.grid > .stackable.stackable.row > .column, .ui.stackable.grid > .column.grid > .column, .ui.stackable.grid > .column.row > .column, .ui.stackable.grid > .column:not(.row), .ui.stackable.grid > .row > .column, .ui.stackable.grid > .row > .wide.column, .ui.stackable.grid > .wide.column.serverstatusWrapper {
padding: 0rem 0rem !important;
}
#serverstatus.green{
border-bottom: 0px solid transparent !important;
}
.greybackground.statustab{
border-top-right-radius: 0em !important;
padding: 2em 2em !important;
}
.standardContainer{
padding-left: 1.2em;
padding-right: 1.2em;
@ -301,8 +304,12 @@ body{
color: #5e5d5d;
}
.ui.segment{
box-shadow: none !important;
}
.ui.secondary.vertical.menu .active.item{
background-color: #414141;
background: var(--theme_background);
font-weight: 600;
color: white;
}
@ -311,11 +318,20 @@ body{
animation: blinker 3s ease-in-out infinite;
}
.ui.important.basic.segment{
background: linear-gradient(217deg, rgba(234,238,175,1) 16%, rgba(254,255,242,1) 78%);
border-radius: 1em;
}
.basic.segment.advanceoptions{
background-color: #f7f7f7;
border-radius: 1em;
}
.bluefont{
color: #417ac1 !important;
}
@keyframes blinker {
50% {
opacity: 50%;
@ -327,52 +343,26 @@ body{
*/
#serverstatus{
height: 100%;
border-radius: 1em;
margin: 1em;
}
#statusTitle{
font-weight: 300;
}
.statustab{
border-radius: 0 !important;
}
.greybackground.statustab{
background-color: #414141 !important;
color: white;
}
.greybackground.statustab .ui.header:not(:first-child){
margin-top: 1em;
}
.greybackground.statustab span,
.greybackground.statustab h1,
.greybackground.statustab h2,
.greybackground.statustab h3,
.greybackground.statustab h4,
.greybackground.statustab h5 {
color: white !important;
}
.greybackground.statustab .header{
color: #b7b7b7 !important;
}
#serverstatus.green{
background-color: #fefefe !important;
border-right: 5px solid #3d9c64;
background: linear-gradient(60deg, #27e7ff, #00ca52);
}
#serverstatus.green .sub.header{
color: rgb(224, 224, 224);
}
#serverstatus.green i,
#serverstatus.green #statusTitle{
color: #3d9c64;
color: rgb(255, 255, 255);
}
#serverstatus.green #statusText{
color: #2c583d;
color: rgb(255, 255, 255);
}
@ -381,22 +371,20 @@ body{
}
#serverstatus:not(.green){
background-color: white !important;
background-image: url("img/plant.png");
background-position: right;
background-repeat: no-repeat;
background-size: auto 100%;
background: linear-gradient(215deg, rgba(38,60,71,1) 13%, rgba(2,3,42,1) 84%);
}
#serverstatus:not(.green) #statusTitle,
#serverstatus:not(.green) i,
#serverstatus:not(.green) .sub.header{
color: #4c4c4c;
color: white;
}
.statustab{
min-height: 5.5em;
margin: 1em;
border-radius: 1em !important;
}
#summaryTotalCount{
@ -467,7 +455,7 @@ body{
transform: scale(1);
}
50% {
background-color: #3d9c64;
background-color: white;
transform: scale(1.5);
}
100% {

View File

@ -7,12 +7,14 @@
<link rel="icon" type="image/png" href="./favicon.png" />
<title>Account Reset | Zoraxy</title>
<link rel="stylesheet" href="script/semantic/semantic.min.css">
<link href="script/aos.css" rel="stylesheet">
<script src="script/aos.js"></script>
<script type="application/javascript" src="script/jquery-3.6.0.min.js"></script>
<script type="application/javascript" src="script/semantic/semantic.min.js"></script>
<style>
body {
background: rgb(245,245,245);
background: linear-gradient(28deg, rgba(245,245,245,1) 63%, rgba(255,255,255,1) 100%);
background: rgb(38,60,71);
background: linear-gradient(215deg, rgba(38,60,71,1) 13%, rgba(2,3,42,1) 84%);
}
.background{
@ -34,23 +36,6 @@
margin:auto;
}
#loginForm{
height: 100%;
background-color: white;
width: 25em;
margin-left: 10em;
margin-top: 0 !important;
margin-bottom: 0 !important;
}
@media all and (max-width: 550px) {
/* CSS rules here for screens lower than 750px */
#loginForm{
width: calc(100% - 4em);
margin-left: 2em;
}
}
#errmsg{
color: #9f3a38;
margin-top: 1em;
@ -61,18 +46,102 @@
.backBtn{
position: absolute;
top: 0em;
left: 1em;
margin-top: -4em;
left: 2em;
transition: opacity 0.3s linear;
}
.backBtn:hover{
opacity: 0.8;
}
#loginForm {
border-radius: 1em;
width: 25em;
height: 550px;
position: absolute; /*Can also be `fixed`*/
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
/*Solves a problem in which the content is being cut when the div is smaller than its' wrapper:*/
max-width: 100%;
max-height: 100%;
overflow: auto;
background-color: white;
}
.wavebase {
position:fixed;
bottom: 0;
left: 0;
width: 100%;
height:5vh;
text-align:center;
padding-top: 1em;
background-color: white;
}
/*
Waves CSS
*/
#wavesWrapper{
position: fixed;
bottom: 5vh;
width: 100%;
left: 0;
}
.waves {
position:relative;
width: 100%;
height:15vh;
margin-bottom:-7px; /*Fix for safari gap*/
min-height:100px;
max-height:150px;
}
.parallax > use {
animation: move-forever 25s cubic-bezier(.55,.5,.45,.5) infinite;
}
.parallax > use:nth-child(1) {
animation-delay: -8s;
animation-duration: 28s;
}
.parallax > use:nth-child(2) {
animation-delay: -12s;
animation-duration: 40s;
}
.parallax > use:nth-child(3) {
animation-delay: -16s;
animation-duration: 52s;
}
.parallax > use:nth-child(4) {
animation-delay: -20s;
animation-duration: 80s;
}
@keyframes move-forever {
0% {
transform: translate3d(-90px,0,0);
}
100% {
transform: translate3d(85px,0,0);
}
}
/*Shrinking for mobile*/
@media (max-width: 768px) {
.waves {
height:40px;
min-height:40px;
}
}
</style>
</head>
<body>
<div class="background"></div>
<div id="loginForm" class="ui middle aligned center aligned grid">
<div id="loginForm" class="ui middle aligned center aligned grid" data-aos="fade-up">
<div class="column">
<a class="backBtn" href="/">
<i class="huge black chevron circle left icon"></i>
</a>
<form class="ui large form">
<div class="ui basic segment">
<img class="ui fluid image" src="img/public/logo.svg" style="pointer-events:none;">
@ -95,7 +164,7 @@
<input id="magic" type="password" name="New password" placeholder="New Password">
</div>
</div>
<div id="resetBtn" class="ui fluid basic green button">Set New Password</div>
<div id="resetBtn" class="ui fluid basic button"><i class="ui green lock open icon"></i> Reset Password</div>
<div id="errmsg" class="ui red message" style="display: none;">
<i class="red remove icon"></i> Unknown Error Occured
</div>
@ -107,12 +176,36 @@
<a href="#" id="resendEmailLink" onclick="sendResetAccountEmail();">Resend Email</a>
</div>
</div>
<div class="ui divider"></div>
<small>Proudly powered by Zoraxy</small>
</form>
<a class="backBtn" href="/">
<i class="big chevron circle left icon" style="color: #121d37;"></i>
</a>
</div>
</div>
<div id="wavesWrapper">
<!-- CSS waves-->
<svg class="waves" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 24 150 28" preserveAspectRatio="none" shape-rendering="auto">
<defs>
<path id="gentle-wave" d="M-160 44c30 0 58-18 88-18s 58 18 88 18 58-18 88-18 58 18 88 18 v44h-352z" />
</defs>
<g class="parallax">
<use xlink:href="#gentle-wave" x="48" y="0" fill="rgba(255,255,255,0.7" />
<use xlink:href="#gentle-wave" x="48" y="3" fill="rgba(255,255,255,0.5)" />
<use xlink:href="#gentle-wave" x="48" y="5" fill="rgba(255,255,255,0.3)" />
<use xlink:href="#gentle-wave" x="48" y="7" fill="#fff" />
</g>
</svg>
</div>
<div class="wavebase">
<p>Proudly powered by <a href="https://zoraxy.arozos.com" target="_blank">Zoraxy</a></p>
</div>
<script>
AOS.init();
var redirectionAddress = "/";
var loginAddress = "/api/auth/login";
$(".checkbox").checkbox();
@ -155,6 +248,12 @@
var token = $('#token').val();
var newPassword = $('#magic').val();
if (token.trim() == ""){
$("#errmsg").html(`<i class="red circle times icon"></i> Token cannot be empty!`);
$("#errmsg").show();
return;
}
// Send POST request with input values as data
$.post('/api/account/new', { username: username, token: token, newpw: newPassword })
.done(function(data) {

1
src/web/script/aos.css Normal file

File diff suppressed because one or more lines are too long

1
src/web/script/aos.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -41,8 +41,7 @@
<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>
<button class="ui basic button" style="float: right;" onclick="saveCredentials();"><i class="green save icon"></i> Save Credential</button>
<button class="ui basic button" onclick="addCredentialsToEditingList();"><i class="green add icon"></i> Add Credential</button>
</div>
<div class="ui divider"></div>
</div>
@ -69,7 +68,7 @@
<small>Make sure you add the tailing slash for only selecting the files / folder inside that path.</small>
</div>
<div class="field" >
<button class="ui basic button" onclick="addExceptionPath();"><i class="blue add icon"></i> Add Exception</button>
<button class="ui basic button" onclick="addExceptionPath();"><i class="yellow add icon"></i> Add Exception</button>
</div>
<div class="field">
<div class="ui basic message">
@ -99,7 +98,7 @@
let payloadHash = window.location.hash.substr(1);
try{
payloadHash = JSON.parse(decodeURIComponent(payloadHash));
loadBasicAuthCredentials(payloadHash.ept, payloadHash.ep);
loadBasicAuthCredentials(payloadHash.ep);
$("#epname").text(payloadHash.ep);
editingEndpoint = payloadHash;
}catch(ex){
@ -107,13 +106,12 @@
}
}
function loadBasicAuthCredentials(endpointType, uuid){
function loadBasicAuthCredentials(uuid){
$.ajax({
url: "/api/proxy/updateCredentials",
method: "GET",
data: {
ep: uuid,
ptype: endpointType
},
success: function(data){
//Push the existing account to list
@ -163,6 +161,9 @@
// Update the table body with the credentials
updateEditingCredentialList();
//Save the table
saveCredentials();
}
function addExceptionPath(){
@ -175,7 +176,6 @@
$.ajax({
url: "/api/proxy/auth/exceptions/add",
data:{
ptype: editingEndpoint.ept,
ep: editingEndpoint.ep,
prefix: newExclusionPathMatchingPrefix
},
@ -197,7 +197,6 @@
$.ajax({
url: "/api/proxy/auth/exceptions/delete",
data:{
ptype: editingEndpoint.ept,
ep: editingEndpoint.ep,
prefix: matchingPrefix
},
@ -271,6 +270,8 @@
// Update the table body
updateEditingCredentialList();
saveCredentials();
}
function alreadyExists(username){
@ -293,7 +294,6 @@
method: "POST",
data: {
ep: editingEndpoint.ep,
ptype: editingEndpoint.ept,
creds: JSON.stringify(editingCredentials)
},
success: function(data){

View File

@ -119,11 +119,10 @@ func UpdateUptimeMonitorTargets() {
// Generate uptime monitor targets from reverse proxy rules
func GetUptimeTargetsFromReverseProxyRules(dp *dynamicproxy.Router) []*uptime.Target {
subds := dp.GetSDProxyEndpointsAsMap()
vdirs := dp.GetVDProxyEndpointsAsMap()
hosts := dp.GetProxyEndpointsAsMap()
UptimeTargets := []*uptime.Target{}
for subd, target := range subds {
for subd, target := range hosts {
url := "http://" + target.Domain
protocol := "http"
if target.RequireTLS {
@ -139,21 +138,6 @@ func GetUptimeTargetsFromReverseProxyRules(dp *dynamicproxy.Router) []*uptime.Ta
})
}
for vdir, target := range vdirs {
url := "http://" + target.Domain
protocol := "http"
if target.RequireTLS {
url = "https://" + target.Domain
protocol = "https"
}
UptimeTargets = append(UptimeTargets, &uptime.Target{
ID: vdir,
Name: "*" + vdir,
URL: url,
Protocol: protocol,
})
}
return UptimeTargets
}