Added basic auth exception paths

Added feature request from #25
This commit is contained in:
Toby Chui 2023-08-22 23:46:54 +08:00
parent dce58343db
commit 4f7f60188f
11 changed files with 486 additions and 156 deletions

View File

@ -55,6 +55,10 @@ func initAPIs() {
authRouter.HandleFunc("/api/proxy/setIncoming", HandleIncomingPortSet) authRouter.HandleFunc("/api/proxy/setIncoming", HandleIncomingPortSet)
authRouter.HandleFunc("/api/proxy/useHttpsRedirect", HandleUpdateHttpsRedirect) authRouter.HandleFunc("/api/proxy/useHttpsRedirect", HandleUpdateHttpsRedirect)
authRouter.HandleFunc("/api/proxy/requestIsProxied", HandleManagementProxyCheck) authRouter.HandleFunc("/api/proxy/requestIsProxied", HandleManagementProxyCheck)
//Reverse proxy auth related APIs
authRouter.HandleFunc("/api/proxy/auth/exceptions/list", ListProxyBasicAuthExceptionPaths)
authRouter.HandleFunc("/api/proxy/auth/exceptions/add", AddProxyBasicAuthExceptionPaths)
authRouter.HandleFunc("/api/proxy/auth/exceptions/delete", RemoveProxyBasicAuthExceptionPaths)
//TLS / SSL config //TLS / SSL config
authRouter.HandleFunc("/api/cert/tls", handleToggleTLSProxy) authRouter.HandleFunc("/api/cert/tls", handleToggleTLSProxy)

View File

@ -5,7 +5,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"log" "log"
"net/http" "net/http"
"os" "os"
@ -26,16 +25,18 @@ import (
*/ */
type Record struct { type Record struct {
ProxyType string ProxyType string
Rootname string Rootname string
ProxyTarget string ProxyTarget string
UseTLS bool UseTLS bool
SkipTlsValidation bool SkipTlsValidation bool
RequireBasicAuth bool RequireBasicAuth bool
BasicAuthCredentials []*dynamicproxy.BasicAuthCredentials BasicAuthCredentials []*dynamicproxy.BasicAuthCredentials
BasicAuthExceptionRules []*dynamicproxy.BasicAuthExceptionRule
} }
func SaveReverseProxyConfig(proxyConfigRecord *Record) error { // Save a reverse proxy config record to file
func SaveReverseProxyConfigToFile(proxyConfigRecord *Record) error {
//TODO: Make this accept new def types //TODO: Make this accept new def types
os.MkdirAll("./conf/proxy/", 0775) os.MkdirAll("./conf/proxy/", 0775)
filename := getFilenameFromRootName(proxyConfigRecord.Rootname) filename := getFilenameFromRootName(proxyConfigRecord.Rootname)
@ -45,10 +46,19 @@ func SaveReverseProxyConfig(proxyConfigRecord *Record) error {
//Write to file //Write to file
js, _ := json.MarshalIndent(thisRecord, "", " ") js, _ := json.MarshalIndent(thisRecord, "", " ")
return ioutil.WriteFile(filepath.Join("./conf/proxy/", filename), js, 0775) return os.WriteFile(filepath.Join("./conf/proxy/", filename), js, 0775)
} }
func RemoveReverseProxyConfig(rootname string) error { // Save a running reverse proxy endpoint to file (with automatic endpoint to record conversion)
func SaveReverseProxyEndpointToFile(proxyEndpoint *dynamicproxy.ProxyEndpoint) error {
recordToSave, err := ConvertProxyEndpointToRecord(proxyEndpoint)
if err != nil {
return err
}
return SaveReverseProxyConfigToFile(recordToSave)
}
func RemoveReverseProxyConfigFile(rootname string) error {
filename := getFilenameFromRootName(rootname) filename := getFilenameFromRootName(rootname)
removePendingFile := strings.ReplaceAll(filepath.Join("./conf/proxy/", filename), "\\", "/") removePendingFile := strings.ReplaceAll(filepath.Join("./conf/proxy/", filename), "\\", "/")
log.Println("Config Removed: ", removePendingFile) log.Println("Config Removed: ", removePendingFile)
@ -66,8 +76,18 @@ func RemoveReverseProxyConfig(rootname string) error {
// Return ptype, rootname and proxyTarget, error if any // Return ptype, rootname and proxyTarget, error if any
func LoadReverseProxyConfig(filename string) (*Record, error) { func LoadReverseProxyConfig(filename string) (*Record, error) {
thisRecord := Record{} thisRecord := Record{
configContent, err := ioutil.ReadFile(filename) ProxyType: "",
Rootname: "",
ProxyTarget: "",
UseTLS: false,
SkipTlsValidation: false,
RequireBasicAuth: false,
BasicAuthCredentials: []*dynamicproxy.BasicAuthCredentials{},
BasicAuthExceptionRules: []*dynamicproxy.BasicAuthExceptionRule{},
}
configContent, err := os.ReadFile(filename)
if err != nil { if err != nil {
return &thisRecord, err return &thisRecord, err
} }
@ -82,6 +102,22 @@ func LoadReverseProxyConfig(filename string) (*Record, error) {
return &thisRecord, nil 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,
SkipTlsValidation: targetProxyEndpoint.SkipCertValidations,
RequireBasicAuth: targetProxyEndpoint.RequireBasicAuth,
BasicAuthCredentials: targetProxyEndpoint.BasicAuthCredentials,
BasicAuthExceptionRules: targetProxyEndpoint.BasicAuthExceptionRules,
}
return &thisProxyConfigRecord, nil
}
func getFilenameFromRootName(rootname string) string { func getFilenameFromRootName(rootname string) string {
//Generate a filename for this rootname //Generate a filename for this rootname
filename := strings.ReplaceAll(rootname, ".", "_") filename := strings.ReplaceAll(rootname, ".", "_")

View File

@ -98,7 +98,7 @@ func ShutdownSeq() {
fmt.Println("- Closing Statistic Collector") fmt.Println("- Closing Statistic Collector")
statisticCollector.Close() statisticCollector.Close()
if mdnsTickerStop != nil { if mdnsTickerStop != nil {
fmt.Println("- Stopping mDNS Discoverer") fmt.Println("- Stopping mDNS Discoverer (might take a few minutes)")
// Stop the mdns service // Stop the mdns service
mdnsTickerStop <- true mdnsTickerStop <- true
} }

View File

@ -3,6 +3,7 @@ package dynamicproxy
import ( import (
"errors" "errors"
"net/http" "net/http"
"strings"
"imuslab.com/zoraxy/mod/auth" "imuslab.com/zoraxy/mod/auth"
) )
@ -15,6 +16,16 @@ import (
*/ */
func (h *ProxyHandler) handleBasicAuthRouting(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error { func (h *ProxyHandler) handleBasicAuthRouting(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
if len(pe.BasicAuthExceptionRules) > 0 {
//Check if the current path matches the exception rules
for _, exceptionRule := range pe.BasicAuthExceptionRules {
if strings.HasPrefix(r.RequestURI, exceptionRule.PathPrefix) {
//This path is excluded from basic auth
return nil
}
}
}
proxyType := "vdir-auth" proxyType := "vdir-auth"
if pe.ProxyType == ProxyType_Subdomain { if pe.ProxyType == ProxyType_Subdomain {
proxyType = "subd-auth" proxyType = "subd-auth"

View File

@ -246,14 +246,15 @@ func (router *Router) AddVirtualDirectoryProxyService(options *VdirOptions) erro
proxy := dpcore.NewDynamicProxyCore(path, options.RootName, options.SkipCertValidations) proxy := dpcore.NewDynamicProxyCore(path, options.RootName, options.SkipCertValidations)
endpointObject := ProxyEndpoint{ endpointObject := ProxyEndpoint{
ProxyType: ProxyType_Vdir, ProxyType: ProxyType_Vdir,
RootOrMatchingDomain: options.RootName, RootOrMatchingDomain: options.RootName,
Domain: domain, Domain: domain,
RequireTLS: options.RequireTLS, RequireTLS: options.RequireTLS,
SkipCertValidations: options.SkipCertValidations, SkipCertValidations: options.SkipCertValidations,
RequireBasicAuth: options.RequireBasicAuth, RequireBasicAuth: options.RequireBasicAuth,
BasicAuthCredentials: options.BasicAuthCredentials, BasicAuthCredentials: options.BasicAuthCredentials,
Proxy: proxy, BasicAuthExceptionRules: options.BasicAuthExceptionRules,
Proxy: proxy,
} }
router.ProxyEndpoints.Store(options.RootName, &endpointObject) router.ProxyEndpoints.Store(options.RootName, &endpointObject)
@ -271,46 +272,24 @@ func (router *Router) LoadProxy(ptype string, key string) (*ProxyEndpoint, error
if !ok { if !ok {
return nil, errors.New("target proxy not found") return nil, errors.New("target proxy not found")
} }
return proxy.(*ProxyEndpoint), nil
targetProxy := proxy.(*ProxyEndpoint)
targetProxy.parent = router
return targetProxy, nil
} else if ptype == "subd" { } else if ptype == "subd" {
proxy, ok := router.SubdomainEndpoint.Load(key) proxy, ok := router.SubdomainEndpoint.Load(key)
if !ok { if !ok {
return nil, errors.New("target proxy not found") return nil, errors.New("target proxy not found")
} }
return proxy.(*ProxyEndpoint), nil
targetProxy := proxy.(*ProxyEndpoint)
targetProxy.parent = router
return targetProxy, nil
} }
return nil, errors.New("unsupported ptype") return nil, errors.New("unsupported ptype")
} }
/*
Save routing from RP
*/
func (router *Router) SaveProxy(ptype string, key string, newConfig *ProxyEndpoint) {
if ptype == "vdir" {
router.ProxyEndpoints.Store(key, newConfig)
} else if ptype == "subd" {
router.SubdomainEndpoint.Store(key, newConfig)
}
}
/*
Remove routing from RP
*/
func (router *Router) RemoveProxy(ptype string, key string) error {
//fmt.Println(ptype, key)
if ptype == "vdir" {
router.ProxyEndpoints.Delete(key)
return nil
} else if ptype == "subd" {
router.SubdomainEndpoint.Delete(key)
return nil
}
return errors.New("invalid ptype")
}
/* /*
Add an default router for the proxy server Add an default router for the proxy server
*/ */

View File

@ -0,0 +1,68 @@
package dynamicproxy
import "errors"
/*
ProxyEndpoint.go
author: tobychui
This script handle the proxy endpoint object actions
so proxyEndpoint can be handled like a proper oop object
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
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)
}
}
//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
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")
}
//ProxyEndpoint remove provide global access by key
func (router *Router) RemoveProxyEndpointByRootname(proxyType string, rootnameOrMatchingDomain string) error {
targetEpt, err := router.LoadProxy(proxyType, rootnameOrMatchingDomain)
if err != nil {
return err
}
return targetEpt.Remove()
}

View File

@ -34,13 +34,14 @@ func (router *Router) AddSubdomainRoutingService(options *SubdOptions) error {
proxy := dpcore.NewDynamicProxyCore(path, "", options.SkipCertValidations) proxy := dpcore.NewDynamicProxyCore(path, "", options.SkipCertValidations)
router.SubdomainEndpoint.Store(options.MatchingDomain, &ProxyEndpoint{ router.SubdomainEndpoint.Store(options.MatchingDomain, &ProxyEndpoint{
RootOrMatchingDomain: options.MatchingDomain, RootOrMatchingDomain: options.MatchingDomain,
Domain: domain, Domain: domain,
RequireTLS: options.RequireTLS, RequireTLS: options.RequireTLS,
Proxy: proxy, Proxy: proxy,
SkipCertValidations: options.SkipCertValidations, SkipCertValidations: options.SkipCertValidations,
RequireBasicAuth: options.RequireBasicAuth, RequireBasicAuth: options.RequireBasicAuth,
BasicAuthCredentials: options.BasicAuthCredentials, BasicAuthCredentials: options.BasicAuthCredentials,
BasicAuthExceptionRules: options.BasicAuthExceptionRules,
}) })
log.Println("Adding Subdomain Rule: ", options.MatchingDomain+" to "+domain) log.Println("Adding Subdomain Rule: ", options.MatchingDomain+" to "+domain)

View File

@ -59,56 +59,51 @@ type BasicAuthUnhashedCredentials struct {
Password string Password string
} }
// Paths to exclude in basic auth enabled proxy handler
type BasicAuthExceptionRule struct {
PathPrefix string
}
// A proxy endpoint record // A proxy endpoint record
type ProxyEndpoint struct { type ProxyEndpoint struct {
ProxyType int //The type of this proxy, see const def ProxyType int //The type of this proxy, see const def
RootOrMatchingDomain string //Root for vdir or Matching domain for subd RootOrMatchingDomain string //Root for vdir or Matching domain for subd, also act as key
Domain string //Domain or IP to proxy to Domain string //Domain or IP to proxy to
RequireTLS bool //Target domain require TLS RequireTLS bool //Target domain require TLS
SkipCertValidations bool //Set to true to accept self signed certs SkipCertValidations bool //Set to true to accept self signed certs
RequireBasicAuth bool //Set to true to request basic auth before proxy RequireBasicAuth bool //Set to true to request basic auth before proxy
BasicAuthCredentials []*BasicAuthCredentials `json:"-"` BasicAuthCredentials []*BasicAuthCredentials `json:"-"` //Basic auth credentials
Proxy *dpcore.ReverseProxy `json:"-"` BasicAuthExceptionRules []*BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target
Proxy *dpcore.ReverseProxy `json:"-"`
parent *Router
} }
type RootOptions struct { type RootOptions struct {
ProxyLocation string ProxyLocation string
RequireTLS bool RequireTLS bool
SkipCertValidations bool SkipCertValidations bool
RequireBasicAuth bool RequireBasicAuth bool
BasicAuthCredentials []*BasicAuthCredentials BasicAuthCredentials []*BasicAuthCredentials
BasicAuthExceptionRules []*BasicAuthExceptionRule
} }
type VdirOptions struct { type VdirOptions struct {
RootName string RootName string
Domain string Domain string
RequireTLS bool RequireTLS bool
SkipCertValidations bool SkipCertValidations bool
RequireBasicAuth bool RequireBasicAuth bool
BasicAuthCredentials []*BasicAuthCredentials BasicAuthCredentials []*BasicAuthCredentials
BasicAuthExceptionRules []*BasicAuthExceptionRule
} }
type SubdOptions struct { type SubdOptions struct {
MatchingDomain string MatchingDomain string
Domain string Domain string
RequireTLS bool RequireTLS bool
SkipCertValidations bool SkipCertValidations bool
RequireBasicAuth bool RequireBasicAuth bool
BasicAuthCredentials []*BasicAuthCredentials BasicAuthCredentials []*BasicAuthCredentials
BasicAuthExceptionRules []*BasicAuthExceptionRule
} }
/*
type ProxyEndpoint struct {
Root string
Domain string
RequireTLS bool
Proxy *reverseproxy.ReverseProxy `json:"-"`
}
type SubdomainEndpoint struct {
MatchingDomain string
Domain string
RequireTLS bool
Proxy *reverseproxy.ReverseProxy `json:"-"`
}
*/

View File

@ -88,21 +88,23 @@ func ReverseProxtInit() {
}) })
} else if record.ProxyType == "subd" { } else if record.ProxyType == "subd" {
dynamicProxyRouter.AddSubdomainRoutingService(&dynamicproxy.SubdOptions{ dynamicProxyRouter.AddSubdomainRoutingService(&dynamicproxy.SubdOptions{
MatchingDomain: record.Rootname, MatchingDomain: record.Rootname,
Domain: record.ProxyTarget, Domain: record.ProxyTarget,
RequireTLS: record.UseTLS, RequireTLS: record.UseTLS,
SkipCertValidations: record.SkipTlsValidation, SkipCertValidations: record.SkipTlsValidation,
RequireBasicAuth: record.RequireBasicAuth, RequireBasicAuth: record.RequireBasicAuth,
BasicAuthCredentials: record.BasicAuthCredentials, BasicAuthCredentials: record.BasicAuthCredentials,
BasicAuthExceptionRules: record.BasicAuthExceptionRules,
}) })
} else if record.ProxyType == "vdir" { } else if record.ProxyType == "vdir" {
dynamicProxyRouter.AddVirtualDirectoryProxyService(&dynamicproxy.VdirOptions{ dynamicProxyRouter.AddVirtualDirectoryProxyService(&dynamicproxy.VdirOptions{
RootName: record.Rootname, RootName: record.Rootname,
Domain: record.ProxyTarget, Domain: record.ProxyTarget,
RequireTLS: record.UseTLS, RequireTLS: record.UseTLS,
SkipCertValidations: record.SkipTlsValidation, SkipCertValidations: record.SkipTlsValidation,
RequireBasicAuth: record.RequireBasicAuth, RequireBasicAuth: record.RequireBasicAuth,
BasicAuthCredentials: record.BasicAuthCredentials, BasicAuthCredentials: record.BasicAuthCredentials,
BasicAuthExceptionRules: record.BasicAuthExceptionRules,
}) })
} else { } else {
log.Println("Unsupported endpoint type: " + record.ProxyType + ". Skipping " + filepath.Base(conf)) log.Println("Unsupported endpoint type: " + record.ProxyType + ". Skipping " + filepath.Base(conf))
@ -282,7 +284,7 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
RequireBasicAuth: requireBasicAuth, RequireBasicAuth: requireBasicAuth,
BasicAuthCredentials: basicAuthCredentials, BasicAuthCredentials: basicAuthCredentials,
} }
SaveReverseProxyConfig(&thisProxyConfigRecord) SaveReverseProxyConfigToFile(&thisProxyConfigRecord)
//Update utm if exists //Update utm if exists
if uptimeMonitor != nil { if uptimeMonitor != nil {
@ -355,7 +357,7 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
RequireBasicAuth: requireBasicAuth, RequireBasicAuth: requireBasicAuth,
BasicAuthCredentials: targetProxyEntry.BasicAuthCredentials, BasicAuthCredentials: targetProxyEntry.BasicAuthCredentials,
} }
dynamicProxyRouter.RemoveProxy("vdir", thisOption.RootName) targetProxyEntry.Remove()
dynamicProxyRouter.AddVirtualDirectoryProxyService(&thisOption) dynamicProxyRouter.AddVirtualDirectoryProxyService(&thisOption)
} else if eptype == "subd" { } else if eptype == "subd" {
@ -367,7 +369,7 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
RequireBasicAuth: requireBasicAuth, RequireBasicAuth: requireBasicAuth,
BasicAuthCredentials: targetProxyEntry.BasicAuthCredentials, BasicAuthCredentials: targetProxyEntry.BasicAuthCredentials,
} }
dynamicProxyRouter.RemoveProxy("subd", thisOption.MatchingDomain) targetProxyEntry.Remove()
dynamicProxyRouter.AddSubdomainRoutingService(&thisOption) dynamicProxyRouter.AddSubdomainRoutingService(&thisOption)
} }
@ -381,7 +383,7 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
RequireBasicAuth: requireBasicAuth, RequireBasicAuth: requireBasicAuth,
BasicAuthCredentials: targetProxyEntry.BasicAuthCredentials, BasicAuthCredentials: targetProxyEntry.BasicAuthCredentials,
} }
SaveReverseProxyConfig(&thisProxyConfigRecord) SaveReverseProxyConfigToFile(&thisProxyConfigRecord)
utils.SendOK(w) utils.SendOK(w)
} }
@ -398,13 +400,15 @@ func DeleteProxyEndpoint(w http.ResponseWriter, r *http.Request) {
return return
} }
err = dynamicProxyRouter.RemoveProxy(ptype, ep) //Remove the config from runtime
err = dynamicProxyRouter.RemoveProxyEndpointByRootname(ptype, ep)
if err != nil { if err != nil {
utils.SendErrorResponse(w, err.Error()) utils.SendErrorResponse(w, err.Error())
return return
} }
RemoveReverseProxyConfig(ep) //Remove the config from file
RemoveReverseProxyConfigFile(ep)
//Update utm if exists //Update utm if exists
if uptimeMonitor != nil { if uptimeMonitor != nil {
@ -528,19 +532,10 @@ func UpdateProxyBasicAuthCredentials(w http.ResponseWriter, r *http.Request) {
targetProxy.BasicAuthCredentials = mergedCredentials targetProxy.BasicAuthCredentials = mergedCredentials
//Save it to file //Save it to file
thisProxyConfigRecord := Record{ SaveReverseProxyEndpointToFile(targetProxy)
ProxyType: ptype,
Rootname: targetProxy.RootOrMatchingDomain,
ProxyTarget: targetProxy.Domain,
UseTLS: targetProxy.RequireTLS,
SkipTlsValidation: targetProxy.SkipCertValidations,
RequireBasicAuth: targetProxy.RequireBasicAuth,
BasicAuthCredentials: targetProxy.BasicAuthCredentials,
}
SaveReverseProxyConfig(&thisProxyConfigRecord)
//Replace runtime configuration //Replace runtime configuration
dynamicProxyRouter.SaveProxy(ptype, ep, targetProxy) targetProxy.UpdateToRuntime()
utils.SendOK(w) utils.SendOK(w)
} else { } else {
http.Error(w, "invalid usage", http.StatusMethodNotAllowed) http.Error(w, "invalid usage", http.StatusMethodNotAllowed)
@ -548,6 +543,147 @@ func UpdateProxyBasicAuthCredentials(w http.ResponseWriter, r *http.Request) {
} }
// List, Update or Remove the exception paths for basic auth.
func ListProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
}
ep, err := utils.GetPara(r, "ep")
if err != nil {
utils.SendErrorResponse(w, "Invalid ep given")
return
}
ptype, err := utils.GetPara(r, "ptype")
if err != nil {
utils.SendErrorResponse(w, "Invalid ptype given")
return
}
//Load the target proxy object from router
targetProxy, err := dynamicProxyRouter.LoadProxy(ptype, ep)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
//List all the exception paths for this proxy
results := targetProxy.BasicAuthExceptionRules
if results == nil {
//It is a config from a really old version of zoraxy. Overwrite it with empty array
results = []*dynamicproxy.BasicAuthExceptionRule{}
}
js, _ := json.Marshal(results)
utils.SendJSONResponse(w, string(js))
return
}
func AddProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request) {
ep, err := utils.PostPara(r, "ep")
if err != nil {
utils.SendErrorResponse(w, "Invalid ep given")
return
}
ptype, err := utils.PostPara(r, "ptype")
if err != nil {
utils.SendErrorResponse(w, "Invalid ptype given")
return
}
matchingPrefix, err := utils.PostPara(r, "prefix")
if err != nil {
utils.SendErrorResponse(w, "Invalid matching prefix given")
return
}
//Load the target proxy object from router
targetProxy, err := dynamicProxyRouter.LoadProxy(ptype, ep)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
//Check if the prefix starts with /. If not, prepend it
if !strings.HasPrefix(matchingPrefix, "/") {
matchingPrefix = "/" + matchingPrefix
}
//Add a new exception rule if it is not already exists
alreadyExists := false
for _, thisExceptionRule := range targetProxy.BasicAuthExceptionRules {
if thisExceptionRule.PathPrefix == matchingPrefix {
alreadyExists = true
break
}
}
if alreadyExists {
utils.SendErrorResponse(w, "This matching path already exists")
return
}
targetProxy.BasicAuthExceptionRules = append(targetProxy.BasicAuthExceptionRules, &dynamicproxy.BasicAuthExceptionRule{
PathPrefix: strings.TrimSpace(matchingPrefix),
})
//Save configs to runtime and file
targetProxy.UpdateToRuntime()
SaveReverseProxyEndpointToFile(targetProxy)
utils.SendOK(w)
}
func RemoveProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request) {
// Delete a rule
ep, err := utils.PostPara(r, "ep")
if err != nil {
utils.SendErrorResponse(w, "Invalid ep given")
return
}
ptype, err := utils.PostPara(r, "ptype")
if err != nil {
utils.SendErrorResponse(w, "Invalid ptype given")
return
}
matchingPrefix, err := utils.PostPara(r, "prefix")
if err != nil {
utils.SendErrorResponse(w, "Invalid matching prefix given")
return
}
// Load the target proxy object from router
targetProxy, err := dynamicProxyRouter.LoadProxy(ptype, ep)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
newExceptionRuleList := []*dynamicproxy.BasicAuthExceptionRule{}
matchingExists := false
for _, thisExceptionalRule := range targetProxy.BasicAuthExceptionRules {
if thisExceptionalRule.PathPrefix != matchingPrefix {
newExceptionRuleList = append(newExceptionRuleList, thisExceptionalRule)
} else {
matchingExists = true
}
}
if !matchingExists {
utils.SendErrorResponse(w, "target matching rule not exists")
return
}
targetProxy.BasicAuthExceptionRules = newExceptionRuleList
// Save configs to runtime and file
targetProxy.UpdateToRuntime()
SaveReverseProxyEndpointToFile(targetProxy)
utils.SendOK(w)
}
func ReverseProxyStatus(w http.ResponseWriter, r *http.Request) { func ReverseProxyStatus(w http.ResponseWriter, r *http.Request) {
js, _ := json.Marshal(dynamicProxyRouter) js, _ := json.Marshal(dynamicProxyRouter)
utils.SendJSONResponse(w, string(js)) utils.SendJSONResponse(w, string(js))

View File

@ -1,3 +1,13 @@
<style>
.expired.certdate{
font-weight: bolder;
color: #bd001c;
}
.valid.certdate{
color: #31c071;
}
</style>
<div class="standardContainer"> <div class="standardContainer">
<div class="ui basic segment"> <div class="ui basic segment">
<h2>TLS / SSL Certificates</h2> <h2>TLS / SSL Certificates</h2>
@ -111,10 +121,12 @@
return a.Domain > b.Domain return a.Domain > b.Domain
}); });
data.forEach(entry => { data.forEach(entry => {
let isExpired = entry.RemainingDays <= 0;
$("#certifiedDomainList").append(`<tr> $("#certifiedDomainList").append(`<tr>
<td>${entry.Domain}</td> <td>${entry.Domain}</td>
<td>${entry.LastModifiedDate}</td> <td>${entry.LastModifiedDate}</td>
<td>${entry.ExpireDate} (${entry.RemainingDays} days left)</td> <td class="${isExpired?"expired":"valid"} certdate">${entry.ExpireDate} (${!isExpired?entry.RemainingDays+" days left":"Expired"})</td>
<td><button title="Delete key-pair" class="ui mini basic red icon button" onclick="deleteCertificate('${entry.Domain}');"><i class="ui red trash icon"></i></button></td> <td><button title="Delete key-pair" class="ui mini basic red icon button" onclick="deleteCertificate('${entry.Domain}');"><i class="ui red trash icon"></i></button></td>
</tr>`); </tr>`);
}); });

View File

@ -42,40 +42,54 @@
</div> </div>
<div class="field" > <div class="field" >
<button class="ui basic button" onclick="addCredentialsToEditingList();"><i class="blue add icon"></i> Add Credential</button> <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>
</div> </div>
<div class="ui divider"></div> <div class="ui divider"></div>
<div class="field" >
<button class="ui basic button" style="float: right;" onclick="saveCredentials();"><i class="green save icon"></i> Save</button>
<button class="ui basic button" style="float: right;" onclick="cancelCredentialEdit();"><i class="remove icon"></i> Cancel</button>
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="ui divider"></div> <div class="ui divider"></div>
<h3 class="ui header">No-Auth Paths</h3> <h3 class="ui header">Authentication Exclusion Paths</h3>
<div class="scrolling content ui form"> <div class="scrolling content ui form">
<p>Exclude specific paths from the basic auth interface. Useful if you are hosting services require remote API access.</p> <p>Exclude specific directories / paths which contains the following subpath prefix from authentication. Useful if you are hosting services require remote API access.</p>
<table class="ui very basic compacted unstackable celled table"> <table class="ui very basic compacted unstackable celled table">
<thead> <thead>
<tr> <tr>
<th>Username</th> <th>Path Prefix</th>
<th>Password</th> <th>Remove</th>
<th>Remove</th> </tr></thead>
</tr></thead> <tbody id="exclusionPaths">
<tbody id="inlineEditExclusionPaths"> <tr>
<tr> <td colspan="2"><i class="ui green circle check icon"></i> No Path Excluded</td>
<td colspan="3"><i class="ui green circle check icon"></i> No Path Excluded</td> </tr>
</tr> </tbody>
</tbody> </table>
</table> <div class="field">
<div class="field"> <input id="newExclusionPath" type="text" placeholder="/public/api/" autocomplete="off">
<input id="inlineEditExclusionPath" type="text" placeholder="/api" autocomplete="off"> <small>Make sure you add the tailing slash for only selecting the files / folder inside that path.</small>
</div> </div>
<div class="field" > <div class="field" >
<button class="ui basic button" onclick="addCredentialsToEditingList();"><i class="blue add icon"></i> Add Credential</button> <button class="ui basic button" onclick="addExceptionPath();"><i class="blue add icon"></i> Add Exception</button>
</div>
<div class="field">
<div class="ui basic message">
<h4>How to use set excluded paths?</h4>
<p>All request URI that contains the given prefix will be allowed to bypass authentication and <b>the prefix must start with a slash.</b> For example, given the following prefix.<br>
<code>/public/res/</code><br>
<br>
Zoraxy will allow authentication bypass of any subdirectories or resources under the /public/res/ directory. For example, the following paths access will be able to bypass basic auth mechanism under this setting.<br>
<code>/public/res/photo.png</code><br>
<code>/public/res/far/boo/</code></p>
</div> </div>
</div>
<div class="ui divider"></div>
<div class="field" >
<button class="ui basic button" style="float: right;" onclick="closeThisWrapper();">Close</button>
</div>
</div> </div>
<br><br><br><br>
</div> </div>
<script> <script>
let editingCredentials = []; let editingCredentials = [];
@ -151,6 +165,80 @@
updateEditingCredentialList(); updateEditingCredentialList();
} }
function addExceptionPath(){
// Retrieve the username and password input values
var newExclusionPathMatchingPrefix = $('#newExclusionPath').val().trim();
if (newExclusionPathMatchingPrefix == ""){
parent.msgbox("Matching prefix cannot be empty!", false, 5000);
return;
}
$.ajax({
url: "/api/proxy/auth/exceptions/add",
data:{
ptype: editingEndpoint.ept,
ep: editingEndpoint.ep,
prefix: newExclusionPathMatchingPrefix
},
method: "POST",
success: function(data){
if (data.error != undefined){
parent.msgbox(data.error, false, 5000);
}else{
initExceptionPaths();
parent.msgbox("New exception path added", true);
$('#newExclusionPath').val("");
}
}
});
}
function removeExceptionPath(object){
let matchingPrefix = $(object).attr("prefix");
$.ajax({
url: "/api/proxy/auth/exceptions/delete",
data:{
ptype: editingEndpoint.ept,
ep: editingEndpoint.ep,
prefix: matchingPrefix
},
method: "POST",
success: function(data){
if (data.error != undefined){
parent.msgbox(data.error, false, 5000);
}else{
initExceptionPaths();
parent.msgbox("Exception path removed", true);
}
}
});
}
//Load exception paths from server
function initExceptionPaths(){
$.get(`/api/proxy/auth/exceptions/list?ptype=${editingEndpoint.ept}&ep=${editingEndpoint.ep}`, function(data){
if (data.error != undefined){
parent.msgbox(data.error, false, 5000);
}else{
if (data.length == 0){
$("#exclusionPaths").html(` <tr>
<td colspan="2"><i class="ui green circle check icon"></i> No Path Excluded</td>
</tr>`);
}else{
$("#exclusionPaths").html("");
data.forEach(function(rule){
$("#exclusionPaths").append(` <tr>
<td>${rule.PathPrefix}</td>
<td><button class="ui red basic mini icon button" onclick="removeExceptionPath(this);" prefix="${rule.PathPrefix}"><i class="ui red times icon"></i></button></td>
</tr>`);
})
}
}
});
}
initExceptionPaths();
function updateEditingCredentialList() { function updateEditingCredentialList() {
var tableBody = $('#inlineEditBasicAuthCredentialTable'); var tableBody = $('#inlineEditBasicAuthCredentialTable');
tableBody.empty(); tableBody.empty();
@ -195,7 +283,7 @@
return isExists; return isExists;
} }
function cancelCredentialEdit(){ function closeThisWrapper(){
parent.hideSideWrapper(true); parent.hideSideWrapper(true);
} }
@ -213,7 +301,7 @@
parent.msgbox(data.error, false, 6000); parent.msgbox(data.error, false, 6000);
}else{ }else{
parent.msgbox("Credentials Updated"); parent.msgbox("Credentials Updated");
parent.hideSideWrapper(true); //parent.hideSideWrapper(true);
} }
} }
}) })