Преглед на файлове

auto update script executed

Toby Chui преди 1 година
родител
ревизия
6d949b2c3f
променени са 8 файла, в които са добавени 386 реда и са изтрити 84 реда
  1. 4 0
      api.go
  2. 41 6
      config.go
  3. 1 1
      main.go
  4. 8 30
      mod/dynamicproxy/dynamicproxy.go
  5. 68 0
      mod/dynamicproxy/proxyEndpoint.go
  6. 3 1
      mod/dynamicproxy/typedef.go
  7. 151 17
      reverseproxy.go
  8. 110 29
      web/snippet/basicAuthEditor.html

+ 4 - 0
api.go

@@ -55,6 +55,10 @@ func initAPIs() {
 	authRouter.HandleFunc("/api/proxy/setIncoming", HandleIncomingPortSet)
 	authRouter.HandleFunc("/api/proxy/useHttpsRedirect", HandleUpdateHttpsRedirect)
 	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
 	authRouter.HandleFunc("/api/cert/tls", handleToggleTLSProxy)

+ 41 - 6
config.go

@@ -5,7 +5,6 @@ import (
 	"encoding/json"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"log"
 	"net/http"
 	"os"
@@ -36,7 +35,8 @@ type Record struct {
 	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
 	os.MkdirAll("./conf/proxy/", 0775)
 	filename := getFilenameFromRootName(proxyConfigRecord.Rootname)
@@ -46,10 +46,19 @@ func SaveReverseProxyConfig(proxyConfigRecord *Record) error {
 
 	//Write to file
 	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)
 	removePendingFile := strings.ReplaceAll(filepath.Join("./conf/proxy/", filename), "\\", "/")
 	log.Println("Config Removed: ", removePendingFile)
@@ -67,8 +76,18 @@ func RemoveReverseProxyConfig(rootname string) error {
 
 // Return ptype, rootname and proxyTarget, error if any
 func LoadReverseProxyConfig(filename string) (*Record, error) {
-	thisRecord := Record{}
-	configContent, err := ioutil.ReadFile(filename)
+	thisRecord := Record{
+		ProxyType:               "",
+		Rootname:                "",
+		ProxyTarget:             "",
+		UseTLS:                  false,
+		SkipTlsValidation:       false,
+		RequireBasicAuth:        false,
+		BasicAuthCredentials:    []*dynamicproxy.BasicAuthCredentials{},
+		BasicAuthExceptionRules: []*dynamicproxy.BasicAuthExceptionRule{},
+	}
+
+	configContent, err := os.ReadFile(filename)
 	if err != nil {
 		return &thisRecord, err
 	}
@@ -83,6 +102,22 @@ func LoadReverseProxyConfig(filename string) (*Record, error) {
 	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 {
 	//Generate a filename for this rootname
 	filename := strings.ReplaceAll(rootname, ".", "_")

+ 1 - 1
main.go

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

+ 8 - 30
mod/dynamicproxy/dynamicproxy.go

@@ -272,44 +272,22 @@ func (router *Router) LoadProxy(ptype string, key string) (*ProxyEndpoint, error
 		if !ok {
 			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" {
 		proxy, ok := router.SubdomainEndpoint.Load(key)
 		if !ok {
 			return nil, errors.New("target proxy not found")
 		}
-		return proxy.(*ProxyEndpoint), nil
-	}
-
-	return nil, errors.New("unsupported ptype")
-}
-
-/*
-Save routing from RP
-*/
-func (router *Router) SaveProxy(ptype string, key string, newConfig *ProxyEndpoint) {
-	if ptype == "vdir" {
-		router.ProxyEndpoints.Store(key, newConfig)
 
-	} else if ptype == "subd" {
-		router.SubdomainEndpoint.Store(key, newConfig)
+		targetProxy := proxy.(*ProxyEndpoint)
+		targetProxy.parent = router
+		return targetProxy, nil
 	}
 
-}
-
-/*
-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")
+	return nil, errors.New("unsupported ptype")
 }
 
 /*

+ 68 - 0
mod/dynamicproxy/proxyEndpoint.go

@@ -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()
+}

+ 3 - 1
mod/dynamicproxy/typedef.go

@@ -67,7 +67,7 @@ type BasicAuthExceptionRule struct {
 // A proxy endpoint record
 type ProxyEndpoint struct {
 	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
 	RequireTLS              bool                      //Target domain require TLS
 	SkipCertValidations     bool                      //Set to true to accept self signed certs
@@ -75,6 +75,8 @@ type ProxyEndpoint struct {
 	BasicAuthCredentials    []*BasicAuthCredentials   `json:"-"` //Basic auth credentials
 	BasicAuthExceptionRules []*BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target
 	Proxy                   *dpcore.ReverseProxy      `json:"-"`
+
+	parent *Router
 }
 
 type RootOptions struct {

+ 151 - 17
reverseproxy.go

@@ -284,7 +284,7 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
 		RequireBasicAuth:     requireBasicAuth,
 		BasicAuthCredentials: basicAuthCredentials,
 	}
-	SaveReverseProxyConfig(&thisProxyConfigRecord)
+	SaveReverseProxyConfigToFile(&thisProxyConfigRecord)
 
 	//Update utm if exists
 	if uptimeMonitor != nil {
@@ -357,7 +357,7 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
 			RequireBasicAuth:     requireBasicAuth,
 			BasicAuthCredentials: targetProxyEntry.BasicAuthCredentials,
 		}
-		dynamicProxyRouter.RemoveProxy("vdir", thisOption.RootName)
+		targetProxyEntry.Remove()
 		dynamicProxyRouter.AddVirtualDirectoryProxyService(&thisOption)
 
 	} else if eptype == "subd" {
@@ -369,7 +369,7 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
 			RequireBasicAuth:     requireBasicAuth,
 			BasicAuthCredentials: targetProxyEntry.BasicAuthCredentials,
 		}
-		dynamicProxyRouter.RemoveProxy("subd", thisOption.MatchingDomain)
+		targetProxyEntry.Remove()
 		dynamicProxyRouter.AddSubdomainRoutingService(&thisOption)
 	}
 
@@ -383,7 +383,7 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
 		RequireBasicAuth:     requireBasicAuth,
 		BasicAuthCredentials: targetProxyEntry.BasicAuthCredentials,
 	}
-	SaveReverseProxyConfig(&thisProxyConfigRecord)
+	SaveReverseProxyConfigToFile(&thisProxyConfigRecord)
 	utils.SendOK(w)
 }
 
@@ -400,13 +400,15 @@ func DeleteProxyEndpoint(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	err = dynamicProxyRouter.RemoveProxy(ptype, ep)
+	//Remove the config from runtime
+	err = dynamicProxyRouter.RemoveProxyEndpointByRootname(ptype, ep)
 	if err != nil {
 		utils.SendErrorResponse(w, err.Error())
 		return
 	}
 
-	RemoveReverseProxyConfig(ep)
+	//Remove the config from file
+	RemoveReverseProxyConfigFile(ep)
 
 	//Update utm if exists
 	if uptimeMonitor != nil {
@@ -530,19 +532,10 @@ func UpdateProxyBasicAuthCredentials(w http.ResponseWriter, r *http.Request) {
 		targetProxy.BasicAuthCredentials = mergedCredentials
 
 		//Save it to file
-		thisProxyConfigRecord := Record{
-			ProxyType:            ptype,
-			Rootname:             targetProxy.RootOrMatchingDomain,
-			ProxyTarget:          targetProxy.Domain,
-			UseTLS:               targetProxy.RequireTLS,
-			SkipTlsValidation:    targetProxy.SkipCertValidations,
-			RequireBasicAuth:     targetProxy.RequireBasicAuth,
-			BasicAuthCredentials: targetProxy.BasicAuthCredentials,
-		}
-		SaveReverseProxyConfig(&thisProxyConfigRecord)
+		SaveReverseProxyEndpointToFile(targetProxy)
 
 		//Replace runtime configuration
-		dynamicProxyRouter.SaveProxy(ptype, ep, targetProxy)
+		targetProxy.UpdateToRuntime()
 		utils.SendOK(w)
 	} else {
 		http.Error(w, "invalid usage", http.StatusMethodNotAllowed)
@@ -550,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) {
 	js, _ := json.Marshal(dynamicProxyRouter)
 	utils.SendJSONResponse(w, string(js))

+ 110 - 29
web/snippet/basicAuthEditor.html

@@ -42,40 +42,53 @@
                         </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>
                         </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 class="ui divider"></div>
-            <h3 class="ui header">No-Auth Directories</h3>
+            <h3 class="ui header">Authentication Exclusion Paths</h3>
             <div class="scrolling content ui form">
-                <p>Exclude specific directories / paths from the basic auth interface. Useful if you are hosting services require remote API access.</p>
-                    <table class="ui very basic compacted unstackable celled table">
-                        <thead>
-                        <tr>
-                            <th>Path Prefix</th>
-                            <th>Remove</th>
-                        </tr></thead>
-                        <tbody id="exclusionPaths">
-                        <tr>
-                            <td colspan="3"><i class="ui green circle check icon"></i> No Path Excluded</td>
-                        </tr>
-                        </tbody>
-                    </table>
-                    <div class="field">
-                        <input id="newExclusionPath" type="text" placeholder="/public/api/" autocomplete="off">
-                        <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>
+                <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">
+                    <thead>
+                    <tr>
+                        <th>Path Prefix</th>
+                        <th>Remove</th>
+                    </tr></thead>
+                    <tbody id="exclusionPaths">
+                    <tr>
+                        <td colspan="2"><i class="ui green circle check icon"></i> No Path Excluded</td>
+                    </tr>
+                    </tbody>
+                </table>
+                <div class="field">
+                    <input id="newExclusionPath" type="text" placeholder="/public/api/" autocomplete="off">
+                    <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>
+                </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 class="ui divider"></div>
+                <div class="field" >
+                    <button class="ui basic button"  style="float: right;" onclick="closeThisWrapper();">Close</button>
+                </div>
             </div>
-            <br><br>
+            
+            <br><br><br><br>
 
         </div>
         <script>
@@ -154,9 +167,77 @@
 
             function addExceptionPath(){
                 // Retrieve the username and password input values
-                var exclusionPath = $('#newExclusionPath').val();
-                
+                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() {
                 var tableBody = $('#inlineEditBasicAuthCredentialTable');
@@ -202,7 +283,7 @@
                 return isExists;
             }
 
-            function cancelCredentialEdit(){
+            function closeThisWrapper(){
                 parent.hideSideWrapper(true);
             }
 
@@ -220,7 +301,7 @@
                             parent.msgbox(data.error, false, 6000);
                         }else{
                             parent.msgbox("Credentials Updated");
-                            parent.hideSideWrapper(true);
+                            //parent.hideSideWrapper(true);
                         }
                     }
                 })