mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-08-06 13:18:30 +02:00
Compare commits
9 Commits
31ba4f20ae
...
v3.2.3
Author | SHA1 | Date | |
---|---|---|---|
![]() |
eb07917c14 | ||
![]() |
217bc48001 | ||
![]() |
38cfab4a09 | ||
![]() |
217e5e90ff | ||
![]() |
4a37a989a0 | ||
![]() |
eb540b774d | ||
![]() |
26d03f9ad4 | ||
![]() |
650d61ba24 | ||
![]() |
366a44a992 |
@@ -519,8 +519,8 @@
|
||||
</div>
|
||||
<div class="item"><a href="https://github.com/tobychui/zoraxy/wiki" target="_blank">Zoraxy Wiki</a></div>
|
||||
<div class="item"><a href="https://github.com/tobychui/zoraxy" target="_blank">Source Code</a></div>
|
||||
<div class="item"><a href="" target="_blank">Offical Plugin List</a></div>
|
||||
<div class="item"><a href="" target="_blank">Plugin Development Guide</a></div>
|
||||
<div class="item"><a href="https://github.com/aroz-online/zoraxy-official-plugins" target="_blank">Offical Plugin List</a></div>
|
||||
<div class="item"><a href="https://zoraxy.aroz.org/plugins/html/" target="_blank">Plugin Development Guide</a></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="three wide column">
|
||||
|
@@ -318,7 +318,7 @@ If everything is correctly setup, you should see the following page when request
|
||||
|
||||
Example terminal output for requesting `/foobar/*`:
|
||||
|
||||
```
|
||||
```html
|
||||
[2025-05-30 20:44:26.142645] [plugin-manager] [system:info] [Dynamic Capture Example:22964] Request captured by dynamic sniff path: /d_sniff/
|
||||
[2025-05-30 20:44:26.142645] [plugin-manager] [system:info] [Dynamic Capture Example:22964] Header:
|
||||
[2025-05-30 20:44:26.142645] [plugin-manager] [system:info] [Dynamic Capture Example:22964] Method: GET
|
||||
|
@@ -615,7 +615,7 @@ func RenderDebugUI(w http.ResponseWriter, r *http.Request) {
|
||||
:
|
||||
</p>
|
||||
</p>
|
||||
<pre><span class="ts-text is-code">[2025-05-30 20:44:26.142645] [plugin-manager] [system:info] [Dynamic Capture Example:22964] Request captured by dynamic sniff path: /d_sniff/
|
||||
<pre><code class="language-html">[2025-05-30 20:44:26.142645] [plugin-manager] [system:info] [Dynamic Capture Example:22964] Request captured by dynamic sniff path: /d_sniff/
|
||||
[2025-05-30 20:44:26.142645] [plugin-manager] [system:info] [Dynamic Capture Example:22964] Header:
|
||||
[2025-05-30 20:44:26.142645] [plugin-manager] [system:info] [Dynamic Capture Example:22964] Method: GET
|
||||
[2025-05-30 20:44:26.142645] [plugin-manager] [system:info] [Dynamic Capture Example:22964] Hostname: a.localhost
|
||||
@@ -641,7 +641,7 @@ func RenderDebugUI(w http.ResponseWriter, r *http.Request) {
|
||||
[2025-05-30 20:44:26.143165] [plugin-manager] [system:info] [Dynamic Capture Example:22964] Proto: HTTP/2.0
|
||||
[2025-05-30 20:44:26.143165] [plugin-manager] [system:info] [Dynamic Capture Example:22964] ProtoMajor: 2
|
||||
[2025-05-30 20:44:26.143165] [plugin-manager] [system:info] [Dynamic Capture Example:22964] ProtoMinor: 0
|
||||
</span></pre>
|
||||
</code></pre>
|
||||
<div class="ts-divider has-top-spaced-large"></div>
|
||||
<p>
|
||||
<p class="ts-text">
|
||||
|
@@ -193,6 +193,7 @@ func RegisterStaticWebServerAPIs(authRouter *auth.RouterDef) {
|
||||
authRouter.HandleFunc("/api/webserv/stop", staticWebServer.HandleStopServer)
|
||||
authRouter.HandleFunc("/api/webserv/setPort", HandleStaticWebServerPortChange)
|
||||
authRouter.HandleFunc("/api/webserv/setDirList", staticWebServer.SetEnableDirectoryListing)
|
||||
authRouter.HandleFunc("/api/webserv/disableListenAllInterface", staticWebServer.SetDisableListenToAllInterface)
|
||||
/* File Manager */
|
||||
if *allowWebFileManager {
|
||||
authRouter.HandleFunc("/api/fs/list", staticWebServer.FileManager.HandleList)
|
||||
|
39
src/go.mod
39
src/go.mod
@@ -18,33 +18,16 @@ require (
|
||||
github.com/microcosm-cc/bluemonday v1.0.26
|
||||
github.com/monperrus/crawler-user-agents v1.1.0
|
||||
github.com/shirou/gopsutil/v4 v4.25.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/syndtr/goleveldb v1.0.0
|
||||
golang.org/x/net v0.33.0
|
||||
golang.org/x/oauth2 v0.24.0
|
||||
golang.org/x/text v0.21.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/auth v0.13.0 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect
|
||||
github.com/benbjohnson/clock v1.3.0 // indirect
|
||||
github.com/ebitengine/purego v0.8.2 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||
github.com/golang/snappy v0.0.1 // indirect
|
||||
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.128 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
|
||||
github.com/peterhellberg/link v1.2.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||
github.com/vultr/govultr/v3 v3.9.1 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.mongodb.org/mongo-driver v1.12.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute/metadata v0.6.0 // indirect
|
||||
github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
|
||||
@@ -53,6 +36,7 @@ require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.29 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.22 // indirect
|
||||
@@ -82,6 +66,7 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.3 // indirect
|
||||
github.com/aws/smithy-go v1.22.1 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/benbjohnson/clock v1.3.0 // indirect
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
@@ -94,6 +79,7 @@ require (
|
||||
github.com/dnsimple/dnsimple-go v1.7.0 // indirect
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/ebitengine/purego v0.8.2 // indirect
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
||||
@@ -102,11 +88,14 @@ require (
|
||||
github.com/go-jose/go-jose/v4 v4.0.4 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-resty/resty/v2 v2.16.2 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
||||
github.com/goccy/go-json v0.10.4 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.1 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||
github.com/golang/snappy v0.0.1 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/s2a-go v0.1.8 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
|
||||
@@ -119,6 +108,7 @@ require (
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
|
||||
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.128 // indirect
|
||||
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect
|
||||
github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
@@ -155,29 +145,36 @@ require (
|
||||
github.com/nzdjb/go-metaname v1.0.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
|
||||
github.com/ovh/go-ovh v1.6.0 // indirect
|
||||
github.com/peterhellberg/link v1.2.0 // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/pquerna/otp v1.4.0 // indirect
|
||||
github.com/sacloud/api-client-go v0.2.10 // indirect
|
||||
github.com/sacloud/go-http v0.1.8 // indirect
|
||||
github.com/sacloud/iaas-api-go v1.14.0 // indirect
|
||||
github.com/sacloud/packages-go v0.0.10 // indirect
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
|
||||
github.com/softlayer/softlayer-go v1.1.7 // indirect
|
||||
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/stretchr/testify v1.10.0 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1065 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1065 // indirect
|
||||
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||
github.com/transip/gotransip/v6 v6.26.0 // indirect
|
||||
github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec // indirect
|
||||
github.com/vinyldns/go-vinyldns v0.9.16 // indirect
|
||||
github.com/vultr/govultr/v3 v3.9.1 // indirect
|
||||
github.com/yandex-cloud/go-genproto v0.0.0-20241220122821-aeb3b05efd1c // indirect
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20241220131134-2393e243c134 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.mongodb.org/mongo-driver v1.12.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect
|
||||
go.opentelemetry.io/otel v1.29.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 // indirect
|
||||
@@ -187,8 +184,8 @@ require (
|
||||
go.uber.org/ratelimit v0.3.0 // indirect
|
||||
golang.org/x/crypto v0.31.0 // indirect
|
||||
golang.org/x/mod v0.22.0 // indirect
|
||||
golang.org/x/oauth2 v0.24.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/time v0.8.0 // indirect
|
||||
golang.org/x/tools v0.28.0 // indirect
|
||||
google.golang.org/api v0.214.0 // indirect
|
||||
|
@@ -37,6 +37,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@@ -125,7 +126,13 @@ func main() {
|
||||
//Start the finalize sequences
|
||||
finalSequence()
|
||||
|
||||
SystemWideLogger.Println(SYSTEM_NAME + " started. Visit control panel at http://localhost" + *webUIPort)
|
||||
if strings.HasPrefix(*webUIPort, ":") {
|
||||
//Bind to all interfaces, issue #672
|
||||
SystemWideLogger.Println(SYSTEM_NAME + " started. Visit control panel at http://localhost" + *webUIPort)
|
||||
} else {
|
||||
SystemWideLogger.Println(SYSTEM_NAME + " started. Visit control panel at http://" + *webUIPort)
|
||||
}
|
||||
|
||||
err = http.ListenAndServe(*webUIPort, csrfMiddleware(webminPanelMux))
|
||||
|
||||
if err != nil {
|
||||
|
@@ -11,6 +11,7 @@ const (
|
||||
DatabaseKeyResponseHeaders = "responseHeaders"
|
||||
DatabaseKeyResponseClientHeaders = "responseClientHeaders"
|
||||
DatabaseKeyRequestHeaders = "requestHeaders"
|
||||
DatabaseKeyRequestIncludedCookies = "requestIncludedCookies"
|
||||
DatabaseKeyRequestExcludedCookies = "requestExcludedCookies"
|
||||
|
||||
HeaderXForwardedProto = "X-Forwarded-Proto"
|
||||
|
@@ -3,7 +3,6 @@ package forward
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
@@ -28,6 +27,10 @@ type AuthRouterOptions struct {
|
||||
// headers are copied.
|
||||
RequestHeaders []string
|
||||
|
||||
// RequestIncludedCookies is a list of cookie keys that if defined will be the only cookies sent in the request to
|
||||
// the authorization server.
|
||||
RequestIncludedCookies []string
|
||||
|
||||
// RequestExcludedCookies is a list of cookie keys that should be removed from every request sent to the upstream.
|
||||
RequestExcludedCookies []string
|
||||
|
||||
@@ -47,16 +50,18 @@ func NewAuthRouter(options *AuthRouterOptions) *AuthRouter {
|
||||
//Read settings from database if available.
|
||||
options.Database.Read(DatabaseTable, DatabaseKeyAddress, &options.Address)
|
||||
|
||||
responseHeaders, responseClientHeaders, requestHeaders, requestExcludedCookies := "", "", "", ""
|
||||
responseHeaders, responseClientHeaders, requestHeaders, requestIncludedCookies, requestExcludedCookies := "", "", "", "", ""
|
||||
|
||||
options.Database.Read(DatabaseTable, DatabaseKeyResponseHeaders, &responseHeaders)
|
||||
options.Database.Read(DatabaseTable, DatabaseKeyResponseClientHeaders, &responseClientHeaders)
|
||||
options.Database.Read(DatabaseTable, DatabaseKeyRequestHeaders, &requestHeaders)
|
||||
options.Database.Read(DatabaseTable, DatabaseKeyRequestIncludedCookies, &requestIncludedCookies)
|
||||
options.Database.Read(DatabaseTable, DatabaseKeyRequestExcludedCookies, &requestExcludedCookies)
|
||||
|
||||
options.ResponseHeaders = strings.Split(responseHeaders, ",")
|
||||
options.ResponseClientHeaders = strings.Split(responseClientHeaders, ",")
|
||||
options.RequestHeaders = strings.Split(requestHeaders, ",")
|
||||
options.RequestIncludedCookies = strings.Split(requestIncludedCookies, ",")
|
||||
options.RequestExcludedCookies = strings.Split(requestExcludedCookies, ",")
|
||||
|
||||
return &AuthRouter{
|
||||
@@ -82,11 +87,12 @@ func (ar *AuthRouter) HandleAPIOptions(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (ar *AuthRouter) handleOptionsGET(w http.ResponseWriter, r *http.Request) {
|
||||
js, _ := json.Marshal(map[string]interface{}{
|
||||
js, _ := json.Marshal(map[string]any{
|
||||
DatabaseKeyAddress: ar.options.Address,
|
||||
DatabaseKeyResponseHeaders: ar.options.ResponseHeaders,
|
||||
DatabaseKeyResponseClientHeaders: ar.options.ResponseClientHeaders,
|
||||
DatabaseKeyRequestHeaders: ar.options.RequestHeaders,
|
||||
DatabaseKeyRequestIncludedCookies: ar.options.RequestIncludedCookies,
|
||||
DatabaseKeyRequestExcludedCookies: ar.options.RequestExcludedCookies,
|
||||
})
|
||||
|
||||
@@ -108,6 +114,7 @@ func (ar *AuthRouter) handleOptionsPOST(w http.ResponseWriter, r *http.Request)
|
||||
responseHeaders, _ := utils.PostPara(r, DatabaseKeyResponseHeaders)
|
||||
responseClientHeaders, _ := utils.PostPara(r, DatabaseKeyResponseClientHeaders)
|
||||
requestHeaders, _ := utils.PostPara(r, DatabaseKeyRequestHeaders)
|
||||
requestIncludedCookies, _ := utils.PostPara(r, DatabaseKeyRequestIncludedCookies)
|
||||
requestExcludedCookies, _ := utils.PostPara(r, DatabaseKeyRequestExcludedCookies)
|
||||
|
||||
// Write changes to runtime
|
||||
@@ -115,6 +122,7 @@ func (ar *AuthRouter) handleOptionsPOST(w http.ResponseWriter, r *http.Request)
|
||||
ar.options.ResponseHeaders = strings.Split(responseHeaders, ",")
|
||||
ar.options.ResponseClientHeaders = strings.Split(responseClientHeaders, ",")
|
||||
ar.options.RequestHeaders = strings.Split(requestHeaders, ",")
|
||||
ar.options.RequestIncludedCookies = strings.Split(requestIncludedCookies, ",")
|
||||
ar.options.RequestExcludedCookies = strings.Split(requestExcludedCookies, ",")
|
||||
|
||||
// Write changes to database
|
||||
@@ -122,6 +130,7 @@ func (ar *AuthRouter) handleOptionsPOST(w http.ResponseWriter, r *http.Request)
|
||||
ar.options.Database.Write(DatabaseTable, DatabaseKeyResponseHeaders, responseHeaders)
|
||||
ar.options.Database.Write(DatabaseTable, DatabaseKeyResponseClientHeaders, responseClientHeaders)
|
||||
ar.options.Database.Write(DatabaseTable, DatabaseKeyRequestHeaders, requestHeaders)
|
||||
ar.options.Database.Write(DatabaseTable, DatabaseKeyRequestIncludedCookies, requestIncludedCookies)
|
||||
ar.options.Database.Write(DatabaseTable, DatabaseKeyRequestExcludedCookies, requestExcludedCookies)
|
||||
|
||||
utils.SendOK(w)
|
||||
@@ -136,60 +145,40 @@ func (ar *AuthRouter) handleOptionsMethodNotAllowed(w http.ResponseWriter, r *ht
|
||||
// HandleAuthProviderRouting is the internal handler for Forward Auth authentication.
|
||||
func (ar *AuthRouter) HandleAuthProviderRouting(w http.ResponseWriter, r *http.Request) error {
|
||||
if ar.options.Address == "" {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
|
||||
ar.options.Logger.PrintAndLog(LogTitle, "Address not set", nil)
|
||||
|
||||
return ErrInternalServerError
|
||||
return ar.handle500Error(w, nil, "Address not set")
|
||||
}
|
||||
|
||||
// Make a request to Authz Server to verify the request
|
||||
// TODO: Add opt-in support for copying the request body to the forward auth request. Currently it's just an
|
||||
// empty body which is usually fine in most instances. It's likely best to see if anyone wants this feature
|
||||
// as I'm unaware of any specific forward auth implementation that needs it.
|
||||
req, err := http.NewRequest(http.MethodGet, ar.options.Address, nil)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
|
||||
ar.options.Logger.PrintAndLog(LogTitle, "Unable to create request", err)
|
||||
|
||||
return ErrInternalServerError
|
||||
return ar.handle500Error(w, err, "Unable to create request")
|
||||
}
|
||||
|
||||
// TODO: Add opt-in support for copying the request body to the forward auth request.
|
||||
headerCopyIncluded(r.Header, req.Header, ar.options.RequestHeaders, true)
|
||||
headerCookieRedact(r, ar.options.RequestIncludedCookies, false)
|
||||
|
||||
// TODO: Add support for upstream headers.
|
||||
// TODO: Add support for headers from upstream proxies. This will likely involve implementing some form of
|
||||
// proxy specific trust system within Zoraxy.
|
||||
rSetForwardedHeaders(r, req)
|
||||
|
||||
// Make the Authz Request.
|
||||
respForwarded, err := ar.client.Do(req)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
|
||||
ar.options.Logger.PrintAndLog(LogTitle, "Unable to perform forwarded auth due to a request error", err)
|
||||
|
||||
return ErrInternalServerError
|
||||
return ar.handle500Error(w, err, "Unable to perform forwarded auth due to a request error")
|
||||
}
|
||||
|
||||
defer respForwarded.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(respForwarded.Body)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
|
||||
ar.options.Logger.PrintAndLog(LogTitle, "Unable to read response to forward auth request", err)
|
||||
|
||||
return ErrInternalServerError
|
||||
}
|
||||
|
||||
// Responses within the 200-299 range are considered successful and allow the proxy to handle the request.
|
||||
if respForwarded.StatusCode >= http.StatusOK && respForwarded.StatusCode < http.StatusMultipleChoices {
|
||||
if len(ar.options.ResponseClientHeaders) != 0 {
|
||||
headerCopyIncluded(respForwarded.Header, w.Header(), ar.options.ResponseClientHeaders, false)
|
||||
}
|
||||
|
||||
if len(ar.options.RequestExcludedCookies) != 0 {
|
||||
// If the user has specified a list of cookies to be removed from the request, deterministically remove them.
|
||||
headerCookieRedact(r, ar.options.RequestExcludedCookies)
|
||||
}
|
||||
headerCookieRedact(r, ar.options.RequestExcludedCookies, true)
|
||||
|
||||
if len(ar.options.ResponseHeaders) != 0 {
|
||||
// Copy specific user-specified headers from the response of the forward auth request to the request sent to the
|
||||
@@ -197,138 +186,32 @@ func (ar *AuthRouter) HandleAuthProviderRouting(w http.ResponseWriter, r *http.R
|
||||
headerCopyIncluded(respForwarded.Header, w.Header(), ar.options.ResponseHeaders, false)
|
||||
}
|
||||
|
||||
// Return the request to the proxy for forwarding to the backend.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Copy the response.
|
||||
// Copy the unsuccessful response.
|
||||
headerCopyExcluded(respForwarded.Header, w.Header(), nil)
|
||||
|
||||
w.WriteHeader(respForwarded.StatusCode)
|
||||
|
||||
body, err := io.ReadAll(respForwarded.Body)
|
||||
if err != nil {
|
||||
return ar.handle500Error(w, err, "Unable to read response to forward auth request")
|
||||
}
|
||||
|
||||
if _, err = w.Write(body); err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
|
||||
ar.options.Logger.PrintAndLog(LogTitle, "Unable to write response", err)
|
||||
|
||||
return ErrInternalServerError
|
||||
return ar.handle500Error(w, err, "Unable to write response")
|
||||
}
|
||||
|
||||
return ErrUnauthorized
|
||||
}
|
||||
|
||||
func scheme(r *http.Request) string {
|
||||
if r.TLS != nil {
|
||||
return "https"
|
||||
}
|
||||
// handle500Error is func intended on factorizing a commonly repeated functional flow within this provider.
|
||||
func (ar *AuthRouter) handle500Error(w http.ResponseWriter, err error, message string) error {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
|
||||
return "http"
|
||||
}
|
||||
|
||||
func headerCookieRedact(r *http.Request, excluded []string) {
|
||||
original := r.Cookies()
|
||||
|
||||
if len(original) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var cookies []string
|
||||
|
||||
for _, cookie := range original {
|
||||
if stringInSlice(cookie.Name, excluded) {
|
||||
continue
|
||||
}
|
||||
|
||||
cookies = append(cookies, cookie.String())
|
||||
}
|
||||
|
||||
r.Header.Set(HeaderCookie, strings.Join(cookies, "; "))
|
||||
}
|
||||
|
||||
func headerCopyExcluded(original, destination http.Header, excludedHeaders []string) {
|
||||
for key, values := range original {
|
||||
// We should never copy the headers in the below list.
|
||||
if stringInSliceFold(key, doNotCopyHeaders) {
|
||||
continue
|
||||
}
|
||||
|
||||
if stringInSliceFold(key, excludedHeaders) {
|
||||
continue
|
||||
}
|
||||
|
||||
destination[key] = append(destination[key], values...)
|
||||
}
|
||||
}
|
||||
|
||||
func headerCopyIncluded(original, destination http.Header, includedHeaders []string, allIfEmpty bool) {
|
||||
if allIfEmpty && len(includedHeaders) == 0 {
|
||||
headerCopyAll(original, destination)
|
||||
} else {
|
||||
headerCopyIncludedExact(original, destination, includedHeaders)
|
||||
}
|
||||
}
|
||||
|
||||
func headerCopyAll(original, destination http.Header) {
|
||||
for key, values := range original {
|
||||
// We should never copy the headers in the below list, even if they're in the list provided by a user.
|
||||
if stringInSliceFold(key, doNotCopyHeaders) {
|
||||
continue
|
||||
}
|
||||
|
||||
destination[key] = append(destination[key], values...)
|
||||
}
|
||||
}
|
||||
|
||||
func headerCopyIncludedExact(original, destination http.Header, keys []string) {
|
||||
for _, key := range keys {
|
||||
// We should never copy the headers in the below list, even if they're in the list provided by a user.
|
||||
if stringInSliceFold(key, doNotCopyHeaders) {
|
||||
continue
|
||||
}
|
||||
|
||||
if values, ok := original[key]; ok {
|
||||
destination[key] = append(destination[key], values...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func stringInSlice(needle string, haystack []string) bool {
|
||||
if len(haystack) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, v := range haystack {
|
||||
if needle == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func stringInSliceFold(needle string, haystack []string) bool {
|
||||
if len(haystack) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, v := range haystack {
|
||||
if strings.EqualFold(needle, v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func rSetForwardedHeaders(r, req *http.Request) {
|
||||
if r.RemoteAddr != "" {
|
||||
before, _, _ := strings.Cut(r.RemoteAddr, ":")
|
||||
|
||||
if ip := net.ParseIP(before); ip != nil {
|
||||
req.Header.Set(HeaderXForwardedFor, ip.String())
|
||||
}
|
||||
}
|
||||
|
||||
req.Header.Set(HeaderXForwardedMethod, r.Method)
|
||||
req.Header.Set(HeaderXForwardedProto, scheme(r))
|
||||
req.Header.Set(HeaderXForwardedHost, r.Host)
|
||||
req.Header.Set(HeaderXForwardedURI, r.URL.Path)
|
||||
ar.options.Logger.PrintAndLog(LogTitle, message, err)
|
||||
|
||||
return ErrInternalServerError
|
||||
}
|
||||
|
137
src/mod/auth/sso/forward/util.go
Normal file
137
src/mod/auth/sso/forward/util.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package forward
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func scheme(r *http.Request) string {
|
||||
if r.TLS != nil {
|
||||
return "https"
|
||||
}
|
||||
|
||||
return "http"
|
||||
}
|
||||
|
||||
func headerCookieRedact(r *http.Request, names []string, exclude bool) {
|
||||
if len(names) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
original := r.Cookies()
|
||||
|
||||
if len(original) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var cookies []string
|
||||
|
||||
for _, cookie := range original {
|
||||
if exclude && stringInSlice(cookie.Name, names) {
|
||||
continue
|
||||
} else if !exclude && !stringInSlice(cookie.Name, names) {
|
||||
continue
|
||||
}
|
||||
|
||||
cookies = append(cookies, cookie.String())
|
||||
}
|
||||
|
||||
value := strings.Join(cookies, "; ")
|
||||
|
||||
r.Header.Set(HeaderCookie, value)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func headerCopyExcluded(original, destination http.Header, excludedHeaders []string) {
|
||||
for key, values := range original {
|
||||
// We should never copy the headers in the below list.
|
||||
if stringInSliceFold(key, doNotCopyHeaders) {
|
||||
continue
|
||||
}
|
||||
|
||||
if stringInSliceFold(key, excludedHeaders) {
|
||||
continue
|
||||
}
|
||||
|
||||
destination[key] = append(destination[key], values...)
|
||||
}
|
||||
}
|
||||
|
||||
func headerCopyIncluded(original, destination http.Header, includedHeaders []string, allIfEmpty bool) {
|
||||
if allIfEmpty && len(includedHeaders) == 0 {
|
||||
headerCopyAll(original, destination)
|
||||
} else {
|
||||
headerCopyIncludedExact(original, destination, includedHeaders)
|
||||
}
|
||||
}
|
||||
|
||||
func headerCopyAll(original, destination http.Header) {
|
||||
for key, values := range original {
|
||||
// We should never copy the headers in the below list, even if they're in the list provided by a user.
|
||||
if stringInSliceFold(key, doNotCopyHeaders) {
|
||||
continue
|
||||
}
|
||||
|
||||
destination[key] = append(destination[key], values...)
|
||||
}
|
||||
}
|
||||
|
||||
func headerCopyIncludedExact(original, destination http.Header, keys []string) {
|
||||
for key, values := range original {
|
||||
// We should never copy the headers in the below list, even if they're in the list provided by a user.
|
||||
if stringInSliceFold(key, doNotCopyHeaders) {
|
||||
continue
|
||||
}
|
||||
|
||||
if !stringInSliceFold(key, keys) {
|
||||
continue
|
||||
}
|
||||
|
||||
destination[key] = append(destination[key], values...)
|
||||
}
|
||||
}
|
||||
|
||||
func stringInSlice(needle string, haystack []string) bool {
|
||||
if len(haystack) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, v := range haystack {
|
||||
if needle == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func stringInSliceFold(needle string, haystack []string) bool {
|
||||
if len(haystack) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, v := range haystack {
|
||||
if strings.EqualFold(needle, v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func rSetForwardedHeaders(r, req *http.Request) {
|
||||
if r.RemoteAddr != "" {
|
||||
before, _, _ := strings.Cut(r.RemoteAddr, ":")
|
||||
|
||||
if ip := net.ParseIP(before); ip != nil {
|
||||
req.Header.Set(HeaderXForwardedFor, ip.String())
|
||||
}
|
||||
}
|
||||
|
||||
req.Header.Set(HeaderXForwardedMethod, r.Method)
|
||||
req.Header.Set(HeaderXForwardedProto, scheme(r))
|
||||
req.Header.Set(HeaderXForwardedHost, r.Host)
|
||||
req.Header.Set(HeaderXForwardedURI, r.URL.Path)
|
||||
}
|
217
src/mod/auth/sso/forward/util_test.go
Normal file
217
src/mod/auth/sso/forward/util_test.go
Normal file
@@ -0,0 +1,217 @@
|
||||
package forward
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestScheme(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
have *http.Request
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
"ShouldHandleDefault",
|
||||
&http.Request{},
|
||||
"http",
|
||||
},
|
||||
{
|
||||
"ShouldHandleExplicit",
|
||||
&http.Request{
|
||||
TLS: nil,
|
||||
},
|
||||
"http",
|
||||
},
|
||||
{
|
||||
"ShouldHandleHTTPS",
|
||||
&http.Request{
|
||||
TLS: &tls.ConnectionState{},
|
||||
},
|
||||
"https",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert.Equal(t, tc.expected, scheme(tc.have))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHeaderCookieRedact(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
have string
|
||||
names []string
|
||||
expectedInclude string
|
||||
expectedExclude string
|
||||
}{
|
||||
{
|
||||
"ShouldHandleIncludeEmptyWithoutSettings",
|
||||
"",
|
||||
nil,
|
||||
"",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"ShouldHandleIncludeEmptyWithSettings",
|
||||
"",
|
||||
[]string{"include"},
|
||||
"",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"ShouldHandleValueWithoutSettings",
|
||||
"include=value; exclude=value",
|
||||
nil,
|
||||
"include=value; exclude=value",
|
||||
"include=value; exclude=value",
|
||||
},
|
||||
{
|
||||
"ShouldHandleValueWithSettings",
|
||||
"include=value; exclude=value",
|
||||
[]string{"include"},
|
||||
"include=value",
|
||||
"exclude=value",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var include, exclude *http.Request
|
||||
|
||||
include, exclude = &http.Request{Header: http.Header{}}, &http.Request{Header: http.Header{}}
|
||||
|
||||
if tc.have != "" {
|
||||
include.Header.Set(HeaderCookie, tc.have)
|
||||
exclude.Header.Set(HeaderCookie, tc.have)
|
||||
}
|
||||
|
||||
headerCookieRedact(include, tc.names, false)
|
||||
|
||||
assert.Equal(t, tc.expectedInclude, include.Header.Get(HeaderCookie))
|
||||
|
||||
headerCookieRedact(exclude, tc.names, true)
|
||||
|
||||
assert.Equal(t, tc.expectedExclude, exclude.Header.Get(HeaderCookie))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHeaderCopyExcluded(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
original http.Header
|
||||
excluded []string
|
||||
expected http.Header
|
||||
}{
|
||||
{
|
||||
"ShouldHandleNoSettingsNoHeaders",
|
||||
http.Header{},
|
||||
nil,
|
||||
http.Header{},
|
||||
},
|
||||
{
|
||||
"ShouldHandleNoSettingsWithHeaders",
|
||||
http.Header{
|
||||
"Example": []string{"value", "other"},
|
||||
"Exclude": []string{"value", "other"},
|
||||
HeaderUpgrade: []string{"do", "not", "copy"},
|
||||
},
|
||||
nil,
|
||||
http.Header{
|
||||
"Example": []string{"value", "other"},
|
||||
"Exclude": []string{"value", "other"},
|
||||
},
|
||||
},
|
||||
{
|
||||
"ShouldHandleSettingsWithHeaders",
|
||||
http.Header{
|
||||
"Example": []string{"value", "other"},
|
||||
"Exclude": []string{"value", "other"},
|
||||
HeaderUpgrade: []string{"do", "not", "copy"},
|
||||
},
|
||||
[]string{"exclude"},
|
||||
http.Header{
|
||||
"Example": []string{"value", "other"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
headers := http.Header{}
|
||||
|
||||
headerCopyExcluded(tc.original, headers, tc.excluded)
|
||||
|
||||
assert.Equal(t, tc.expected, headers)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHeaderCopyIncluded(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
original http.Header
|
||||
included []string
|
||||
expected http.Header
|
||||
expectedAll http.Header
|
||||
}{
|
||||
{
|
||||
"ShouldHandleNoSettingsNoHeaders",
|
||||
http.Header{},
|
||||
nil,
|
||||
http.Header{},
|
||||
http.Header{},
|
||||
},
|
||||
{
|
||||
"ShouldHandleNoSettingsWithHeaders",
|
||||
http.Header{
|
||||
"Example": []string{"value", "other"},
|
||||
"Include": []string{"value", "other"},
|
||||
HeaderUpgrade: []string{"do", "not", "copy"},
|
||||
},
|
||||
nil,
|
||||
http.Header{},
|
||||
http.Header{
|
||||
"Example": []string{"value", "other"},
|
||||
"Include": []string{"value", "other"},
|
||||
},
|
||||
},
|
||||
{
|
||||
"ShouldHandleSettingsWithHeaders",
|
||||
http.Header{
|
||||
"Example": []string{"value", "other"},
|
||||
"Include": []string{"value", "other"},
|
||||
HeaderUpgrade: []string{"do", "not", "copy"},
|
||||
},
|
||||
[]string{"include"},
|
||||
http.Header{
|
||||
"Include": []string{"value", "other"},
|
||||
},
|
||||
http.Header{
|
||||
"Include": []string{"value", "other"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
headers := http.Header{}
|
||||
|
||||
headerCopyIncluded(tc.original, headers, tc.included, false)
|
||||
|
||||
assert.Equal(t, tc.expected, headers)
|
||||
|
||||
headers = http.Header{}
|
||||
|
||||
headerCopyIncluded(tc.original, headers, tc.included, true)
|
||||
|
||||
assert.Equal(t, tc.expectedAll, headers)
|
||||
})
|
||||
}
|
||||
}
|
@@ -70,8 +70,9 @@ type ResponseRewriteRuleSet struct {
|
||||
DownstreamHeaders [][]string
|
||||
|
||||
/* Advance Usecase Options */
|
||||
HostHeaderOverwrite string //Force overwrite of request "Host" header (advanced usecase)
|
||||
NoRemoveHopByHop bool //Do not remove hop-by-hop headers (advanced usecase)
|
||||
HostHeaderOverwrite string //Force overwrite of request "Host" header (advanced usecase)
|
||||
NoRemoveHopByHop bool //Do not remove hop-by-hop headers (advanced usecase)
|
||||
DisableChunkedTransferEncoding bool //Disable chunked transfer encoding
|
||||
|
||||
/* System Information Payload */
|
||||
Version string //Version number of Zoraxy, use for X-Proxy-By
|
||||
@@ -287,7 +288,7 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
|
||||
rewriteUserAgent(outreq.Header, "Zoraxy/"+rrr.Version)
|
||||
|
||||
//Fix proxmox transfer encoding bug if detected Proxmox Cookie
|
||||
if domainsniff.IsProxmox(req) {
|
||||
if rrr.DisableChunkedTransferEncoding || domainsniff.IsProxmox(req) {
|
||||
outreq.TransferEncoding = []string{"identity"}
|
||||
}
|
||||
|
||||
|
@@ -11,7 +11,6 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/domainsniff"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/rewrite"
|
||||
"imuslab.com/zoraxy/mod/netutils"
|
||||
@@ -186,16 +185,17 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
|
||||
|
||||
//Handle the request reverse proxy
|
||||
statusCode, err := selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
||||
ProxyDomain: selectedUpstream.OriginIpOrDomain,
|
||||
OriginalHost: reqHostname,
|
||||
UseTLS: selectedUpstream.RequireTLS,
|
||||
NoCache: h.Parent.Option.NoCache,
|
||||
PathPrefix: "",
|
||||
UpstreamHeaders: upstreamHeaders,
|
||||
DownstreamHeaders: downstreamHeaders,
|
||||
HostHeaderOverwrite: headerRewriteOptions.RequestHostOverwrite,
|
||||
NoRemoveHopByHop: headerRewriteOptions.DisableHopByHopHeaderRemoval,
|
||||
Version: target.parent.Option.HostVersion,
|
||||
ProxyDomain: selectedUpstream.OriginIpOrDomain,
|
||||
OriginalHost: reqHostname,
|
||||
UseTLS: selectedUpstream.RequireTLS,
|
||||
NoCache: h.Parent.Option.NoCache,
|
||||
PathPrefix: "",
|
||||
UpstreamHeaders: upstreamHeaders,
|
||||
DownstreamHeaders: downstreamHeaders,
|
||||
DisableChunkedTransferEncoding: target.DisableChunkedTransferEncoding,
|
||||
HostHeaderOverwrite: headerRewriteOptions.RequestHostOverwrite,
|
||||
NoRemoveHopByHop: headerRewriteOptions.DisableHopByHopHeaderRemoval,
|
||||
Version: target.parent.Option.HostVersion,
|
||||
})
|
||||
|
||||
//validate the error
|
||||
@@ -244,8 +244,8 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
|
||||
h.Parent.logRequest(r, true, 101, "vdir-websocket", r.Host, target.Domain)
|
||||
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
|
||||
SkipTLSValidation: target.SkipCertValidations,
|
||||
SkipOriginCheck: target.parent.EnableWebsocketCustomHeaders, //You should not use websocket via virtual directory. But keep this to true for compatibility
|
||||
CopyAllHeaders: domainsniff.RequireWebsocketHeaderCopy(r), //Left this as default to prevent nginx user setting / as vdir
|
||||
SkipOriginCheck: true, //You should not use websocket via virtual directory. But keep this to true for compatibility
|
||||
CopyAllHeaders: target.parent.EnableWebsocketCustomHeaders, //Left this as default to prevent nginx user setting / as vdir
|
||||
UserDefinedHeaders: target.parent.HeaderRewriteRules.UserDefinedHeaders,
|
||||
Logger: h.Parent.Option.Logger,
|
||||
})
|
||||
@@ -280,14 +280,15 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
|
||||
|
||||
//Handle the virtual directory reverse proxy request
|
||||
statusCode, err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
||||
ProxyDomain: target.Domain,
|
||||
OriginalHost: reqHostname,
|
||||
UseTLS: target.RequireTLS,
|
||||
PathPrefix: target.MatchingPath,
|
||||
UpstreamHeaders: upstreamHeaders,
|
||||
DownstreamHeaders: downstreamHeaders,
|
||||
HostHeaderOverwrite: headerRewriteOptions.RequestHostOverwrite,
|
||||
Version: target.parent.parent.Option.HostVersion,
|
||||
ProxyDomain: target.Domain,
|
||||
OriginalHost: reqHostname,
|
||||
UseTLS: target.RequireTLS,
|
||||
PathPrefix: target.MatchingPath,
|
||||
UpstreamHeaders: upstreamHeaders,
|
||||
DownstreamHeaders: downstreamHeaders,
|
||||
DisableChunkedTransferEncoding: target.parent.DisableChunkedTransferEncoding,
|
||||
HostHeaderOverwrite: headerRewriteOptions.RequestHostOverwrite,
|
||||
Version: target.parent.parent.Option.HostVersion,
|
||||
})
|
||||
|
||||
var dnsError *net.DNSError
|
||||
|
@@ -194,6 +194,9 @@ type ProxyEndpoint struct {
|
||||
//Uptime Monitor
|
||||
DisableUptimeMonitor bool //Disable uptime monitor for this endpoint
|
||||
|
||||
// Chunked Transfer Encoding
|
||||
DisableChunkedTransferEncoding bool //Disable chunked transfer encoding for this endpoint
|
||||
|
||||
//Access Control
|
||||
AccessFilterUUID string //Access filter ID
|
||||
|
||||
|
@@ -17,22 +17,24 @@ import (
|
||||
*/
|
||||
|
||||
type StaticWebServerStatus struct {
|
||||
ListeningPort int
|
||||
EnableDirectoryListing bool
|
||||
WebRoot string
|
||||
Running bool
|
||||
EnableWebDirManager bool
|
||||
ListeningPort int
|
||||
EnableDirectoryListing bool
|
||||
WebRoot string
|
||||
Running bool
|
||||
EnableWebDirManager bool
|
||||
DisableListenToAllInterface bool
|
||||
}
|
||||
|
||||
// Handle getting current static web server status
|
||||
func (ws *WebServer) HandleGetStatus(w http.ResponseWriter, r *http.Request) {
|
||||
listeningPortInt, _ := strconv.Atoi(ws.option.Port)
|
||||
currentStatus := StaticWebServerStatus{
|
||||
ListeningPort: listeningPortInt,
|
||||
EnableDirectoryListing: ws.option.EnableDirectoryListing,
|
||||
WebRoot: ws.option.WebRoot,
|
||||
Running: ws.isRunning,
|
||||
EnableWebDirManager: ws.option.EnableWebDirManager,
|
||||
ListeningPort: listeningPortInt,
|
||||
EnableDirectoryListing: ws.option.EnableDirectoryListing,
|
||||
WebRoot: ws.option.WebRoot,
|
||||
Running: ws.isRunning,
|
||||
EnableWebDirManager: ws.option.EnableWebDirManager,
|
||||
DisableListenToAllInterface: ws.option.DisableListenToAllInterface,
|
||||
}
|
||||
|
||||
js, _ := json.Marshal(currentStatus)
|
||||
@@ -91,3 +93,19 @@ func (ws *WebServer) SetEnableDirectoryListing(w http.ResponseWriter, r *http.Re
|
||||
ws.option.EnableDirectoryListing = enableList
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
// Get or set disable listen to all interface settings
|
||||
func (ws *WebServer) SetDisableListenToAllInterface(w http.ResponseWriter, r *http.Request) {
|
||||
disableListen, err := utils.PostBool(r, "disable")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid setting given")
|
||||
return
|
||||
}
|
||||
err = ws.option.Sysdb.Write("webserv", "disableListenToAllInterface", disableListen)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "unable to save setting")
|
||||
return
|
||||
}
|
||||
ws.option.DisableListenToAllInterface = disableListen
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
@@ -25,13 +25,21 @@ import (
|
||||
//go:embed templates/*
|
||||
var templates embed.FS
|
||||
|
||||
/*
|
||||
WebServerOptions define the default option for the webserv
|
||||
might get override by user settings loaded from db
|
||||
|
||||
Any changes in here might need to also update the StaticWebServerStatus struct
|
||||
in handler.go. See handler.go for more information.
|
||||
*/
|
||||
type WebServerOptions struct {
|
||||
Port string //Port for listening
|
||||
EnableDirectoryListing bool //Enable listing of directory
|
||||
WebRoot string //Folder for stroing the static web folders
|
||||
EnableWebDirManager bool //Enable web file manager to handle files in web directory
|
||||
Logger *logger.Logger //System logger
|
||||
Sysdb *database.Database //Database for storing configs
|
||||
Port string //Port for listening
|
||||
EnableDirectoryListing bool //Enable listing of directory
|
||||
WebRoot string //Folder for stroing the static web folders
|
||||
EnableWebDirManager bool //Enable web file manager to handle files in web directory
|
||||
DisableListenToAllInterface bool // Disable listening to all interfaces, only listen to localhost
|
||||
Logger *logger.Logger //System logger
|
||||
Sysdb *database.Database //Database for storing configs
|
||||
}
|
||||
|
||||
type WebServer struct {
|
||||
@@ -92,6 +100,11 @@ func (ws *WebServer) RestorePreviousState() {
|
||||
ws.option.Sysdb.Read("webserv", "dirlist", &enableDirList)
|
||||
ws.option.EnableDirectoryListing = enableDirList
|
||||
|
||||
//Set disable listen to all interface
|
||||
disableListenToAll := ws.option.DisableListenToAllInterface
|
||||
ws.option.Sysdb.Read("webserv", "disableListenToAllInterface", &disableListenToAll)
|
||||
ws.option.DisableListenToAllInterface = disableListenToAll
|
||||
|
||||
//Check the running state
|
||||
webservRunning := true
|
||||
ws.option.Sysdb.Read("webserv", "enabled", &webservRunning)
|
||||
@@ -156,8 +169,12 @@ func (ws *WebServer) Start() error {
|
||||
fs := http.FileServer(http.Dir(filepath.Join(ws.option.WebRoot, "html")))
|
||||
ws.mux.Handle("/", ws.fsMiddleware(fs))
|
||||
|
||||
listenAddr := ":" + ws.option.Port
|
||||
if ws.option.DisableListenToAllInterface {
|
||||
listenAddr = "127.0.0.1:" + ws.option.Port
|
||||
}
|
||||
ws.server = &http.Server{
|
||||
Addr: ":" + ws.option.Port,
|
||||
Addr: listenAddr,
|
||||
Handler: ws.mux,
|
||||
}
|
||||
|
||||
|
@@ -556,6 +556,9 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
proxyRateLimit = 1000
|
||||
}
|
||||
|
||||
// Disable chunked Encoding
|
||||
disableChunkedEncoding, _ := utils.PostBool(r, "dChunkedEnc")
|
||||
|
||||
//Load the previous basic auth credentials from current proxy rules
|
||||
targetProxyEntry, err := dynamicProxyRouter.LoadProxy(rootNameOrMatchingDomain)
|
||||
if err != nil {
|
||||
@@ -596,6 +599,7 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
newProxyEndpoint.RateLimit = proxyRateLimit
|
||||
newProxyEndpoint.UseStickySession = useStickySession
|
||||
newProxyEndpoint.DisableUptimeMonitor = disbleUtm
|
||||
newProxyEndpoint.DisableChunkedTransferEncoding = disableChunkedEncoding
|
||||
newProxyEndpoint.Tags = tags
|
||||
|
||||
//Prepare to replace the current routing rule
|
||||
|
@@ -223,10 +223,10 @@
|
||||
<div id="httprpEditModalSideMenu" class="four wide column">
|
||||
<div class="ui secondary fluid vertical menu">
|
||||
<a class="active item hrpedit_menu_item" cfgpage="downstream">
|
||||
<i class="angle double white right icon"></i> <span class="editorSideMenuText">Downstream</span>
|
||||
<i class="home icon"></i> <span class="editorSideMenuText">Host</span>
|
||||
</a>
|
||||
<a class="item hrpedit_menu_item" cfgpage="upstream">
|
||||
<i class="angle double left icon"></i> <span class="editorSideMenuText">Upstream</span>
|
||||
<i class="server icon"></i> <span class="editorSideMenuText">Destinations</span>
|
||||
</a>
|
||||
<a class="item hrpedit_menu_item" cfgpage="vdirs">
|
||||
<i class="angle folder icon"></i> <span class="editorSideMenuText">Virtual Directory</span>
|
||||
@@ -256,7 +256,7 @@
|
||||
</div>
|
||||
<div id="httprpEditModalContentWindow" class="twelve wide column">
|
||||
<div style="height:100%;">
|
||||
<!-- Downstream -->
|
||||
<!-- Host -->
|
||||
<div class="rpconfig_content" rpcfg="downstream">
|
||||
<div class="ui segment">
|
||||
<h3>
|
||||
@@ -288,10 +288,11 @@
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- Upstream -->
|
||||
<!-- Destinations -->
|
||||
<div class="rpconfig_content" rpcfg="upstream">
|
||||
<div class="ui segment">
|
||||
<div class="upstream_list">
|
||||
<b>Enabled Upstreams</b>
|
||||
<div class="upstream_list" style="margin-top: 0.4em;">
|
||||
|
||||
</div>
|
||||
<button class="ui basic compact button editUpstreamButton" style="margin-left: 0.4em; margin-top: 1em;"><i class="grey server icon"></i> Edit Upstreams</button>
|
||||
@@ -308,17 +309,18 @@
|
||||
<small>Enable stick session on load balancing</small></label>
|
||||
</div>
|
||||
<br>
|
||||
<div class="ui disabled checkbox" style="margin-top: 0.4em;">
|
||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
<input type="checkbox" class="DisableChunkedTransferEncoding">
|
||||
<label>Disable Chunked Transfer Encoding<br>
|
||||
<small>Enable this option if your upstream uses a legacy HTTP server implementation</small></label>
|
||||
<small>Enable this option if your upstream uses a legacy HTTP server implementation (e.g. Proxmox / opencloud)</small></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Virtual Directories-->
|
||||
<div class="rpconfig_content" rpcfg="vdirs">
|
||||
<div class="ui segment">
|
||||
<div class="vdir_list">
|
||||
<b>List of Virtual Directories</b>
|
||||
<div class="vdir_list" style="margin-top:0.4em;">
|
||||
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
@@ -719,6 +721,7 @@
|
||||
let requireRateLimit = $(editor).find(".RequireRateLimit")[0].checked;
|
||||
let rateLimit = $(editor).find(".RateLimit").val();
|
||||
let bypassGlobalTLS = $(editor).find(".BypassGlobalTLS")[0].checked;
|
||||
let disableChunkedTransferEncoding = $(editor).find(".DisableChunkedTransferEncoding")[0].checked;
|
||||
let tags = getTagsArrayFromEndpoint(uuid);
|
||||
if (tags.length > 0){
|
||||
tags = tags.join(",");
|
||||
@@ -726,7 +729,7 @@
|
||||
tags = "";
|
||||
}
|
||||
|
||||
console.log({
|
||||
cfgPayload = {
|
||||
"type": epttype,
|
||||
"rootname": uuid,
|
||||
"ss":useStickySession,
|
||||
@@ -734,24 +737,16 @@
|
||||
"bpgtls": bypassGlobalTLS,
|
||||
"authprovider" :authProviderType,
|
||||
"rate" :requireRateLimit,
|
||||
"dChunkedEnc": disableChunkedTransferEncoding,
|
||||
"ratenum" :rateLimit,
|
||||
"tags": tags,
|
||||
});
|
||||
};
|
||||
console.log("updating proxy config:", cfgPayload);
|
||||
|
||||
$.cjax({
|
||||
url: "/api/proxy/edit",
|
||||
method: "POST",
|
||||
data: {
|
||||
"type": epttype,
|
||||
"rootname": uuid,
|
||||
"ss":useStickySession,
|
||||
"dutm": DisableUptimeMonitor,
|
||||
"bpgtls": bypassGlobalTLS,
|
||||
"authprovider" :authProviderType,
|
||||
"rate" :requireRateLimit,
|
||||
"ratenum" :rateLimit,
|
||||
"tags": tags,
|
||||
},
|
||||
data: cfgPayload,
|
||||
success: function(data){
|
||||
if (data.error !== undefined){
|
||||
msgbox(data.error, false, 6000);
|
||||
@@ -1143,6 +1138,12 @@
|
||||
saveProxyInlineEdit(uuid);
|
||||
});
|
||||
|
||||
editor.find(".DisableChunkedTransferEncoding").off("change");
|
||||
editor.find(".DisableChunkedTransferEncoding").prop("checked", subd.DisableChunkedTransferEncoding);
|
||||
editor.find(".DisableChunkedTransferEncoding").on("change", function() {
|
||||
saveProxyInlineEdit(uuid);
|
||||
});
|
||||
|
||||
/* ------------ Vdirs ------------ */
|
||||
editor.find(".vdir_list").html(renderVirtualDirectoryList(subd));
|
||||
editor.find(".editVdirBtn").off("click").on("click", function(){
|
||||
|
@@ -1,797 +0,0 @@
|
||||
<div class="standardContainer">
|
||||
<div class="ui basic segment">
|
||||
<h2>HTTP Proxy</h2>
|
||||
<p>Proxy HTTP server with HTTP or HTTPS for multiple hosts. If you are only proxying for one host / domain, use Default Site instead.</p>
|
||||
</div>
|
||||
<style>
|
||||
#httpProxyList .ui.toggle.checkbox input:checked ~ label::before{
|
||||
background-color: #00ca52 !important;
|
||||
}
|
||||
|
||||
.subdEntry td:not(.ignoremw){
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.httpProxyListTools{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tag-select{
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tag-select:hover{
|
||||
text-decoration: underline;
|
||||
opacity: 0.8;
|
||||
}
|
||||
</style>
|
||||
<div class="httpProxyListTools" style="margin-bottom: 1em;">
|
||||
<div id="tagFilterDropdown" class="ui floating basic dropdown labeled icon button" style="min-width: 150px;">
|
||||
<i class="filter icon"></i>
|
||||
<span class="text">Filter by tags</span>
|
||||
<div class="menu">
|
||||
<div class="ui icon search input">
|
||||
<i class="search icon"></i>
|
||||
<input type="text" placeholder="Search tags...">
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<div class="scrolling menu tagList">
|
||||
<!--
|
||||
Example:
|
||||
<div class="item">
|
||||
<div class="ui red empty circular label"></div>
|
||||
Important
|
||||
</div>
|
||||
-->
|
||||
<!-- Add more tag options dynamically -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui small input" style="width: 300px; height: 38px;">
|
||||
<!-- Prevent the browser from filling the saved Zoraxy login account into the input searchInput below -->
|
||||
<input type="password" autocomplete="off" hidden/>
|
||||
<input type="text" id="searchInput" placeholder="Quick Search" onkeydown="handleSearchInput(event);" onchange="handleSearchInput(event);" onblur="handleSearchInput(event);">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="width: 100%; overflow-x: auto; margin-bottom: 1em; min-height: 300px;">
|
||||
<table class="ui celled sortable unstackable compact table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Host</th>
|
||||
<th>Destination</th>
|
||||
<th>Virtual Directory</th>
|
||||
<th>Tags</th>
|
||||
<th style="max-width: 300px;">Advanced Settings</th>
|
||||
<th class="no-sort" style="min-width:150px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="httpProxyList">
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<button class="ui icon right floated basic button" onclick="listProxyEndpoints();"><i class="green refresh icon"></i> Refresh</button>
|
||||
<br><br>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
/* List all proxy endpoints */
|
||||
function listProxyEndpoints(){
|
||||
$.get("/api/proxy/list?type=host", function(data){
|
||||
$("#httpProxyList").html(``);
|
||||
if (data.error !== undefined){
|
||||
$("#httpProxyList").append(`<tr>
|
||||
<td data-label="" colspan="5"><i class="remove icon"></i> ${data.error}</td>
|
||||
</tr>`);
|
||||
}else if (data.length == 0){
|
||||
$("#httpProxyList").append(`<tr>
|
||||
<td data-label="" colspan="5"><i class="green check circle icon"></i> No HTTP Proxy Record</td>
|
||||
</tr>`);
|
||||
}else{
|
||||
//Sort by RootOrMatchingDomain field
|
||||
data.sort((a,b) => (a.RootOrMatchingDomain > b.RootOrMatchingDomain) ? 1 : ((b.RootOrMatchingDomain > a.RootOrMatchingDomain) ? -1 : 0))
|
||||
data.forEach(subd => {
|
||||
let subdData = encodeURIComponent(JSON.stringify(subd));
|
||||
|
||||
//Build the upstream list
|
||||
let upstreams = "";
|
||||
if (subd.ActiveOrigins.length == 0){
|
||||
//Invalid config
|
||||
upstreams = `<i class="ui yellow exclamation triangle icon"></i> No Active Upstream Origin<br>`;
|
||||
}else{
|
||||
subd.ActiveOrigins.forEach(upstream => {
|
||||
console.log(upstream);
|
||||
//Check if the upstreams require TLS connections
|
||||
let tlsIcon = "";
|
||||
if (upstream.RequireTLS){
|
||||
tlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
|
||||
if (upstream.SkipCertValidations){
|
||||
tlsIcon = `<i class="yellow lock icon" title="TLS/SSL mode without verification"></i>`
|
||||
}
|
||||
}
|
||||
|
||||
let upstreamLink = `${upstream.RequireTLS?"https://":"http://"}${upstream.OriginIpOrDomain}`;
|
||||
|
||||
upstreams += `<a href="${upstreamLink}" target="_blank">${upstream.OriginIpOrDomain} ${tlsIcon}</a><br>`;
|
||||
})
|
||||
}
|
||||
|
||||
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>`;
|
||||
}
|
||||
|
||||
//Build the virtual directory list
|
||||
var vdList = `<div class="ui list">`;
|
||||
subd.VirtualDirectories.forEach(vdir => {
|
||||
vdList += `<div class="item">${vdir.MatchingPath} <i class="green angle double right icon"></i> ${vdir.Domain}</div>`;
|
||||
});
|
||||
vdList += `</div>`;
|
||||
|
||||
if (subd.VirtualDirectories.length == 0){
|
||||
vdList = `<small style="opacity: 0.3; pointer-events: none; user-select: none;">No Virtual Directory</small>`;
|
||||
}
|
||||
|
||||
let enableChecked = "checked";
|
||||
if (subd.Disabled){
|
||||
enableChecked = "";
|
||||
}
|
||||
let httpProto = "http://";
|
||||
if ($("#tls").checkbox("is checked")) {
|
||||
httpProto = "https://";
|
||||
} else {
|
||||
httpProto = "http://";
|
||||
}
|
||||
let hostnameRedirectPort = currentListeningPort;
|
||||
if (hostnameRedirectPort == 80 || hostnameRedirectPort == 443){
|
||||
hostnameRedirectPort = "";
|
||||
}else{
|
||||
hostnameRedirectPort = ":" + hostnameRedirectPort;
|
||||
}
|
||||
let aliasDomains = ``;
|
||||
if (subd.MatchingDomainAlias != undefined && subd.MatchingDomainAlias.length > 0){
|
||||
aliasDomains = `<small class="aliasDomains" eptuuid="${subd.RootOrMatchingDomain}" style="color: #636363;">Alias: `;
|
||||
subd.MatchingDomainAlias.forEach(alias => {
|
||||
aliasDomains += `<a href="${httpProto}${alias}${hostnameRedirectPort}" target="_blank">${alias}</a>, `;
|
||||
});
|
||||
aliasDomains = aliasDomains.substr(0, aliasDomains.length - 2); //Remove the last tailing seperator
|
||||
aliasDomains += `</small><br>`;
|
||||
}
|
||||
|
||||
$("#httpProxyList").append(`<tr eptuuid="${subd.RootOrMatchingDomain}" payload="${subdData}" class="subdEntry">
|
||||
<td data-label="" editable="true" datatype="inbound">
|
||||
<a href="${httpProto}${subd.RootOrMatchingDomain}${hostnameRedirectPort}" target="_blank">${subd.RootOrMatchingDomain}</a> ${inboundTlsIcon}<br>
|
||||
${aliasDomains}
|
||||
<small class="accessRuleNameUnderHost" ruleid="${subd.AccessFilterUUID}"></small>
|
||||
</td>
|
||||
<td data-label="" editable="true" datatype="domain">
|
||||
<div class="upstreamList">
|
||||
${upstreams}
|
||||
</div>
|
||||
</td>
|
||||
<td data-label="" editable="true" datatype="vdir">${vdList}</td>
|
||||
<td data-label="tags" payload="${encodeURIComponent(JSON.stringify(subd.Tags))}" datatype="tags">
|
||||
<div class="tags-list">
|
||||
${subd.Tags.length >0 ? subd.Tags.map(tag => `<span class="ui tiny label tag-select" style="background-color: ${getTagColorByName(tag)}; color: ${getTagTextColor(tag)}">${tag}</span>`).join(""):"<small style='opacity: 0.3; pointer-events: none; user-select: none;'>No Tags</small>"}
|
||||
</div>
|
||||
</td>
|
||||
<td data-label="" editable="true" datatype="advanced" style="width: 350px;">
|
||||
${subd.AuthenticationProvider.AuthMethod == 0x1?`<i class="ui grey key icon"></i> Basic Auth`:``}
|
||||
${subd.AuthenticationProvider.AuthMethod == 0x2?`<i class="ui blue key icon"></i> Forward Auth`:``}
|
||||
${subd.AuthenticationProvider.AuthMethod == 0x3?`<i class="ui yellow key icon"></i> OAuth2`:``}
|
||||
${subd.AuthenticationProvider.AuthMethod != 0x0 && subd.RequireRateLimit?"<br>":""}
|
||||
${subd.RequireRateLimit?`<i class="ui green check icon"></i> Rate Limit @ ${subd.RateLimit} req/s`:``}
|
||||
${subd.AuthenticationProvider.AuthMethod == 0x0 && !subd.RequireRateLimit?`<small style="opacity: 0.3; pointer-events: none; user-select: none;">No Special Settings</small>`:""}
|
||||
</td>
|
||||
<td class="center aligned ignoremw" editable="true" datatype="action" data-label="">
|
||||
<div class="ui toggle tiny fitted checkbox" style="margin-bottom: -0.5em; margin-right: 0.4em;" title="Enable / Disable Rule">
|
||||
<input type="checkbox" class="enableToggle" name="active" ${enableChecked} eptuuid="${subd.RootOrMatchingDomain}" onchange="handleProxyRuleToggle(this);">
|
||||
<label></label>
|
||||
</div>
|
||||
<button title="Edit Proxy Rule" class="ui circular mini basic icon button editBtn inlineEditActionBtn" onclick='editEndpoint("${(subd.RootOrMatchingDomain).hexEncode()}")'><i class="edit icon"></i></button>
|
||||
<button title="Remove Proxy Rule" class="ui circular mini red basic icon button inlineEditActionBtn" onclick='deleteEndpoint("${(subd.RootOrMatchingDomain).hexEncode()}")'><i class="trash icon"></i></button>
|
||||
</td>
|
||||
</tr>`);
|
||||
});
|
||||
populateTagFilterDropdown(data);
|
||||
}
|
||||
|
||||
resolveAccessRuleNameOnHostRPlist();
|
||||
});
|
||||
}
|
||||
|
||||
//Perform realtime alias update without refreshing the whole page
|
||||
function updateAliasListForEndpoint(endpointName, newAliasDomainList){
|
||||
let targetEle = $(`.aliasDomains[eptuuid='${endpointName}']`);
|
||||
console.log(targetEle);
|
||||
if (targetEle.length == 0){
|
||||
return;
|
||||
}
|
||||
|
||||
let aliasDomains = ``;
|
||||
if (newAliasDomainList != undefined && newAliasDomainList.length > 0){
|
||||
aliasDomains = `Alias: `;
|
||||
newAliasDomainList.forEach(alias => {
|
||||
aliasDomains += `<a href="//${alias}" target="_blank">${alias}</a>, `;
|
||||
});
|
||||
aliasDomains = aliasDomains.substr(0, aliasDomains.length - 2); //Remove the last tailing seperator
|
||||
$(targetEle).html(aliasDomains);
|
||||
$(targetEle).show();
|
||||
}else{
|
||||
$(targetEle).hide();
|
||||
}
|
||||
}
|
||||
|
||||
//Resolve & Update all rule names on host PR list
|
||||
function resolveAccessRuleNameOnHostRPlist(){
|
||||
//Resolve the access filters
|
||||
$.get("/api/access/list", function(data){
|
||||
console.log(data);
|
||||
if (data.error == undefined){
|
||||
//Build a map base on the data
|
||||
let accessRuleMap = {};
|
||||
for (var i = 0; i < data.length; i++){
|
||||
accessRuleMap[data[i].ID] = data[i];
|
||||
}
|
||||
|
||||
|
||||
$(".accessRuleNameUnderHost").each(function(){
|
||||
let thisAccessRuleID = $(this).attr("ruleid");
|
||||
if (thisAccessRuleID== ""){
|
||||
thisAccessRuleID = "default"
|
||||
}
|
||||
|
||||
if (thisAccessRuleID == "default"){
|
||||
//No need to label default access rules
|
||||
$(this).html("");
|
||||
return;
|
||||
}
|
||||
|
||||
let rule = accessRuleMap[thisAccessRuleID];
|
||||
if (rule == undefined){
|
||||
//Missing config or config too old
|
||||
$(this).html(`<i class="ui red exclamation triangle icon"></i> <b style="color: #db2828;">Access Rule Error</b>`);
|
||||
return;
|
||||
}
|
||||
let icon = `<i class="ui grey filter icon"></i>`;
|
||||
if (rule.ID == "default"){
|
||||
icon = `<i class="ui yellow star icon"></i>`;
|
||||
}else if (rule.BlacklistEnabled && !rule.WhitelistEnabled){
|
||||
//This is a blacklist filter
|
||||
icon = `<i class="ui red filter icon"></i>`;
|
||||
}else if (rule.WhitelistEnabled && !rule.BlacklistEnabled){
|
||||
//This is a whitelist filter
|
||||
icon = `<i class="ui green filter icon"></i>`;
|
||||
}else if (rule.WhitelistEnabled && rule.BlacklistEnabled){
|
||||
//Whitelist and blacklist filter
|
||||
icon = `<i class="ui yellow filter icon"></i>`;
|
||||
}
|
||||
|
||||
if (rule != undefined){
|
||||
$(this).html(`${icon} ${rule.Name}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//Update the access rule name on given epuuid, call by hostAccessEditor.html
|
||||
function updateAccessRuleNameUnderHost(epuuid, newruleUID){
|
||||
$(`tr[eptuuid='${epuuid}'].subdEntry`).find(".accessRuleNameUnderHost").attr("ruleid", newruleUID);
|
||||
resolveAccessRuleNameOnHostRPlist();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Inline editor for httprp.html
|
||||
*/
|
||||
|
||||
function editEndpoint(uuid) {
|
||||
uuid = uuid.hexDecode();
|
||||
var row = $('tr[eptuuid="' + uuid + '"]');
|
||||
var columns = row.find('td[data-label]');
|
||||
var payload = $(row).attr("payload");
|
||||
payload = JSON.parse(decodeURIComponent(payload));
|
||||
console.log(payload);
|
||||
columns.each(function(index) {
|
||||
var column = $(this);
|
||||
var oldValue = column.text().trim();
|
||||
|
||||
if ($(this).attr("editable") == "false"){
|
||||
//This col do not allow edit. Skip
|
||||
return;
|
||||
}
|
||||
|
||||
// Create an input element based on the column content
|
||||
var input;
|
||||
var datatype = $(this).attr("datatype");
|
||||
if (datatype == "domain"){
|
||||
let useStickySessionChecked = "";
|
||||
if (payload.UseStickySession){
|
||||
useStickySessionChecked = "checked";
|
||||
}
|
||||
|
||||
let enableUptimeMonitor = "";
|
||||
//Note the config file store the uptime monitor as disable, so we need to reverse the logic
|
||||
if (!payload.DisableUptimeMonitor){
|
||||
enableUptimeMonitor = "checked";
|
||||
}
|
||||
|
||||
input = `<button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 1em;" onclick="editUpstreams('${uuid}');"><i class="grey server icon"></i> Edit Upstreams</button>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
<input type="checkbox" class="UseStickySession" ${useStickySessionChecked}>
|
||||
<label>Use Sticky Session<br>
|
||||
<small>Enable stick session on load balancing</small></label>
|
||||
</div>
|
||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
<input type="checkbox" class="EnableUptimeMonitor" ${enableUptimeMonitor}>
|
||||
<label>Monitor Uptime<br>
|
||||
<small>Enable active uptime monitor</small></label>
|
||||
</div>
|
||||
`;
|
||||
column.append(input);
|
||||
$(column).find(".upstreamList").addClass("editing");
|
||||
}else if (datatype == "vdir"){
|
||||
//Append a quick access button for vdir page
|
||||
column.append(`<button class="ui basic tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="quickEditVdir('${uuid}');">
|
||||
<i class="ui yellow folder icon"></i> Edit Virtual Directories
|
||||
</button>`);
|
||||
}else if (datatype == "tags"){
|
||||
column.append(`
|
||||
<div class="ui divider"></div>
|
||||
<button class="ui basic compact fluid tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editTags('${uuid}');"><i class="ui purple tag icon"></i> Edit tags</button>
|
||||
`);
|
||||
}else if (datatype == "advanced"){
|
||||
let authProvider = payload.AuthenticationProvider.AuthMethod;
|
||||
|
||||
let skipWebSocketOriginCheck = payload.SkipWebSocketOriginCheck;
|
||||
let wsCheckstate = "";
|
||||
if (skipWebSocketOriginCheck){
|
||||
wsCheckstate = "checked";
|
||||
}
|
||||
|
||||
let requireRateLimit = payload.RequireRateLimit;
|
||||
let rateLimitCheckState = "";
|
||||
if (requireRateLimit){
|
||||
rateLimitCheckState = "checked";
|
||||
}
|
||||
let rateLimit = payload.RateLimit;
|
||||
if (rateLimit == 0){
|
||||
//This value is not set. Make it default to 100
|
||||
rateLimit = 100;
|
||||
}
|
||||
let rateLimitDisableState = "";
|
||||
if (!payload.RequireRateLimit){
|
||||
rateLimitDisableState = "disabled";
|
||||
}
|
||||
|
||||
column.empty().append(`
|
||||
<div class="grouped fields authProviderPicker">
|
||||
<label><b>Authentication Provider</b></label>
|
||||
<div class="field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" value="0" name="authProviderType" ${authProvider==0x0?"checked":""}>
|
||||
<label>None (Anyone can access)</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" value="1" name="authProviderType" ${authProvider==0x1?"checked":""}>
|
||||
<label>Basic Auth</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" value="2" name="authProviderType" ${authProvider==0x2?"checked":""}>
|
||||
<label>Forward Auth</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" value="3" name="authProviderType" ${authProvider==0x3?"checked":""}>
|
||||
<label>OAuth2</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editBasicAuthCredentials('${uuid}');"><i class="ui blue user circle icon"></i> Edit Credentials</button>
|
||||
<button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editCustomHeaders('${uuid}');"><i class="heading icon"></i> Custom Headers</button>
|
||||
|
||||
<div class="ui basic advance segment" style="padding: 0.4em !important; border-radius: 0.4em;">
|
||||
<div class="ui endpointAdvanceConfig accordion" style="padding-right: 0.6em;">
|
||||
<div class="title">
|
||||
<i class="dropdown icon"></i>
|
||||
Security Options
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
<input type="checkbox" onchange="handleToggleRateLimitInput();" class="RequireRateLimit" ${rateLimitCheckState}>
|
||||
<label>Require Rate Limit<br>
|
||||
<small>Check this to enable rate limit on this inbound hostname</small></label>
|
||||
</div><br>
|
||||
<div class="ui mini right labeled fluid input ${rateLimitDisableState}" style="margin-top: 0.4em;">
|
||||
<input type="number" class="RateLimit" value="${rateLimit}" min="1" >
|
||||
<label class="ui basic label">
|
||||
req / sec / IP
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
`);
|
||||
|
||||
$('.authProviderPicker .ui.checkbox').checkbox();
|
||||
} else if (datatype == "ratelimit"){
|
||||
|
||||
column.empty().append(`
|
||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
<input type="checkbox" class="RequireRateLimit" ${checkstate}>
|
||||
<label>Require Rate Limit</label>
|
||||
</div>
|
||||
<div class="ui mini fluid input">
|
||||
<input type="number" class="RateLimit" value="${rateLimit}" placeholder="100" min="1" max="1000" >
|
||||
</div>
|
||||
`);
|
||||
|
||||
}else if (datatype == 'action'){
|
||||
column.empty().append(`
|
||||
<button title="Save" onclick="saveProxyInlineEdit('${uuid.hexEncode()}');" class="ui basic small icon circular button inlineEditActionBtn"><i class="ui green save icon"></i></button>
|
||||
<button title="Cancel" onclick="exitProxyInlineEdit();" class="ui basic small icon circular button inlineEditActionBtn"><i class="ui remove icon"></i></button>
|
||||
|
||||
`);
|
||||
}else if (datatype == "inbound"){
|
||||
let originalContent = $(column).html();
|
||||
|
||||
//Check if this host is covered within one of the certificates. If not, show the icon
|
||||
let enableQuickRequestButton = true;
|
||||
let domains = [payload.RootOrMatchingDomain]; //Domain for getting certificate if needed
|
||||
for (var i = 0; i < payload.MatchingDomainAlias.length; i++){
|
||||
let thisAliasName = payload.MatchingDomainAlias[i];
|
||||
domains.push(thisAliasName);
|
||||
}
|
||||
|
||||
//Check if the domain or alias contains wildcard, if yes, disabled the get certificate button
|
||||
if (payload.RootOrMatchingDomain.indexOf("*") > -1){
|
||||
enableQuickRequestButton = false;
|
||||
}
|
||||
|
||||
if (payload.MatchingDomainAlias != undefined){
|
||||
for (var i = 0; i < payload.MatchingDomainAlias.length; i++){
|
||||
if (payload.MatchingDomainAlias[i].indexOf("*") > -1){
|
||||
enableQuickRequestButton = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//encode the domain to DOM
|
||||
let certificateDomains = encodeURIComponent(JSON.stringify(domains));
|
||||
|
||||
column.empty().append(`${originalContent}
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
<input type="checkbox" class="BypassGlobalTLS" ${payload.BypassGlobalTLS?"checked":""}>
|
||||
<label>Allow plain HTTP access<br>
|
||||
<small>Allow inbound connections without TLS/SSL</small></label>
|
||||
</div><br>
|
||||
<button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editAliasHostnames('${uuid}');"><i class=" blue at icon"></i> Alias</button>
|
||||
<button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editAccessRule('${uuid}');"><i class="ui filter icon"></i> Access Rule</button>
|
||||
<button class="ui basic compact tiny ${enableQuickRequestButton?"":"disabled"} button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="requestCertificateForExistingHost('${uuid}', '${certificateDomains}', this);"><i class="green lock icon"></i> Get Certificate</button>
|
||||
`);
|
||||
|
||||
$(".hostAccessRuleSelector").dropdown();
|
||||
}else{
|
||||
//Unknown field. Leave it untouched
|
||||
}
|
||||
});
|
||||
|
||||
$(".endpointAdvanceConfig").accordion();
|
||||
$("#httpProxyList").find(".editBtn").addClass("disabled");
|
||||
}
|
||||
|
||||
//handleToggleRateLimitInput will get trigger if the "require rate limit" checkbox
|
||||
// is changed and toggle the disable state of the rate limit input field
|
||||
function handleToggleRateLimitInput(){
|
||||
let isRateLimitEnabled = $("#httpProxyList input.RequireRateLimit")[0].checked;
|
||||
if (isRateLimitEnabled){
|
||||
$("#httpProxyList input.RateLimit").parent().removeClass("disabled");
|
||||
}else{
|
||||
$("#httpProxyList input.RateLimit").parent().addClass("disabled");
|
||||
}
|
||||
}
|
||||
|
||||
function exitProxyInlineEdit(){
|
||||
listProxyEndpoints();
|
||||
$("#httpProxyList").find(".editBtn").removeClass("disabled");
|
||||
}
|
||||
|
||||
function saveProxyInlineEdit(uuid){
|
||||
uuid = uuid.hexDecode();
|
||||
var row = $('tr[eptuuid="' + uuid + '"]');
|
||||
if (row.length == 0){
|
||||
return;
|
||||
}
|
||||
|
||||
var epttype = "host";
|
||||
let useStickySession = $(row).find(".UseStickySession")[0].checked;
|
||||
let DisableUptimeMonitor = !$(row).find(".EnableUptimeMonitor")[0].checked;
|
||||
let authProviderType = $(row).find(".authProviderPicker input[type='radio']:checked").val();
|
||||
let requireRateLimit = $(row).find(".RequireRateLimit")[0].checked;
|
||||
let rateLimit = $(row).find(".RateLimit").val();
|
||||
let bypassGlobalTLS = $(row).find(".BypassGlobalTLS")[0].checked;
|
||||
let tags = getTagsArrayFromEndpoint(uuid);
|
||||
if (tags.length > 0){
|
||||
tags = tags.join(",");
|
||||
}else{
|
||||
tags = "";
|
||||
}
|
||||
$.cjax({
|
||||
url: "/api/proxy/edit",
|
||||
method: "POST",
|
||||
data: {
|
||||
"type": epttype,
|
||||
"rootname": uuid,
|
||||
"ss":useStickySession,
|
||||
"dutm": DisableUptimeMonitor,
|
||||
"bpgtls": bypassGlobalTLS,
|
||||
"authprovider" :authProviderType,
|
||||
"rate" :requireRateLimit,
|
||||
"ratenum" :rateLimit,
|
||||
"tags": tags,
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error !== undefined){
|
||||
msgbox(data.error, false, 6000);
|
||||
}else{
|
||||
msgbox("Proxy endpoint updated");
|
||||
listProxyEndpoints();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//Generic functions for delete rp endpoints
|
||||
function deleteEndpoint(epoint){
|
||||
epoint = decodeURIComponent(epoint).hexDecode();
|
||||
if (confirm("Confirm remove proxy for :" + epoint + "?")){
|
||||
$.cjax({
|
||||
url: "/api/proxy/del",
|
||||
method: "POST",
|
||||
data: {ep: epoint},
|
||||
success: function(data){
|
||||
if (data.error == undefined){
|
||||
listProxyEndpoints();
|
||||
msgbox("Proxy Rule Deleted", true);
|
||||
reloadUptimeList();
|
||||
}else{
|
||||
msgbox(data.error, false);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* button events */
|
||||
function editBasicAuthCredentials(uuid){
|
||||
let payload = encodeURIComponent(JSON.stringify({
|
||||
ept: "host",
|
||||
ep: uuid
|
||||
}));
|
||||
showSideWrapper("snippet/basicAuthEditor.html?t=" + Date.now() + "#" + payload);
|
||||
}
|
||||
|
||||
function editAccessRule(uuid){
|
||||
let payload = encodeURIComponent(JSON.stringify({
|
||||
ept: "host",
|
||||
ep: uuid
|
||||
}));
|
||||
showSideWrapper("snippet/hostAccessEditor.html?t=" + Date.now() + "#" + payload);
|
||||
}
|
||||
|
||||
function editAliasHostnames(uuid){
|
||||
let payload = encodeURIComponent(JSON.stringify({
|
||||
ept: "host",
|
||||
ep: uuid
|
||||
}));
|
||||
showSideWrapper("snippet/aliasEditor.html?t=" + Date.now() + "#" + payload);
|
||||
}
|
||||
|
||||
function quickEditVdir(uuid){
|
||||
openTabById("vdir");
|
||||
$("#vdirBaseRoutingRule").parent().dropdown("set selected", uuid);
|
||||
}
|
||||
|
||||
//Open the custom header editor
|
||||
function editCustomHeaders(uuid){
|
||||
let payload = encodeURIComponent(JSON.stringify({
|
||||
ept: "host",
|
||||
ep: uuid
|
||||
}));
|
||||
showSideWrapper("snippet/customHeaders.html?t=" + Date.now() + "#" + payload);
|
||||
}
|
||||
|
||||
//Open the load balance option
|
||||
function editUpstreams(uuid){
|
||||
let payload = encodeURIComponent(JSON.stringify({
|
||||
ept: "host",
|
||||
ep: uuid
|
||||
}));
|
||||
showSideWrapper("snippet/upstreams.html?t=" + Date.now() + "#" + payload);
|
||||
}
|
||||
|
||||
function handleProxyRuleToggle(object){
|
||||
let endpointUUID = $(object).attr("eptuuid");
|
||||
let isChecked = object.checked;
|
||||
$.cjax({
|
||||
url: "/api/proxy/toggle",
|
||||
data: {
|
||||
"ep": endpointUUID,
|
||||
"enable": isChecked
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false);
|
||||
}else{
|
||||
if (isChecked){
|
||||
msgbox("Proxy Rule Enabled");
|
||||
}else{
|
||||
msgbox("Proxy Rule Disabled");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
Certificate Shortcut
|
||||
*/
|
||||
|
||||
function requestCertificateForExistingHost(hostUUID, RootAndAliasDomains, btn=undefined){
|
||||
RootAndAliasDomains = JSON.parse(decodeURIComponent(RootAndAliasDomains))
|
||||
let renewDomainKey = RootAndAliasDomains.join(",");
|
||||
let preferedACMEEmail = $("#prefACMEEmail").val();
|
||||
if (preferedACMEEmail == ""){
|
||||
msgbox("Preferred email for ACME registration not set", false);
|
||||
return;
|
||||
}
|
||||
let defaultCA = $("#defaultCA").dropdown("get value");
|
||||
if (defaultCA == ""){
|
||||
defaultCA = "Let's Encrypt";
|
||||
}
|
||||
|
||||
//Check if the root or the alias domain contain wildcard character, if yes, return error
|
||||
for (var i = 0; i < RootAndAliasDomains.length; i++){
|
||||
if (RootAndAliasDomains[i].indexOf("*") != -1){
|
||||
msgbox("Wildcard domain can only be setup via ACME tool", false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//Renew the certificate
|
||||
renewCertificate(renewDomainKey, false, btn);
|
||||
}
|
||||
|
||||
//Bind on tab switch events
|
||||
tabSwitchEventBind["httprp"] = function(){
|
||||
listProxyEndpoints();
|
||||
|
||||
//Reset the tag filter
|
||||
$("#tagFilterDropdown").dropdown('set selected', "");
|
||||
}
|
||||
|
||||
/* Tags & Search */
|
||||
function handleSearchInput(event){
|
||||
if (event.key == "Escape"){
|
||||
$("#searchInput").val("");
|
||||
}
|
||||
filterProxyList();
|
||||
}
|
||||
|
||||
// Function to filter the proxy list
|
||||
function filterProxyList() {
|
||||
let searchInput = $("#searchInput").val().toLowerCase();
|
||||
let selectedTag = $("#tagFilterDropdown").dropdown('get value');
|
||||
$("#httpProxyList tr").each(function() {
|
||||
let host = $(this).find("td[data-label='']").text().toLowerCase();
|
||||
let tagElements = $(this).find("td[data-label='tags']");
|
||||
let tags = tagElements.attr("payload");
|
||||
tags = JSON.parse(decodeURIComponent(tags));
|
||||
if ((host.includes(searchInput) || searchInput === "") && (tags.includes(selectedTag) || selectedTag === "")) {
|
||||
$(this).show();
|
||||
} else {
|
||||
$(this).hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Function to generate a color based on a tag name
|
||||
function getTagColorByName(tagName) {
|
||||
function hashCode(str) {
|
||||
return str.split('').reduce((prevHash, currVal) =>
|
||||
((prevHash << 5) - prevHash) + currVal.charCodeAt(0), 0);
|
||||
}
|
||||
let hash = hashCode(tagName);
|
||||
let color = '#' + ((hash >> 24) & 0xFF).toString(16).padStart(2, '0') +
|
||||
((hash >> 16) & 0xFF).toString(16).padStart(2, '0') +
|
||||
((hash >> 8) & 0xFF).toString(16).padStart(2, '0');
|
||||
return color;
|
||||
}
|
||||
|
||||
function getTagTextColor(tagName){
|
||||
let color = getTagColorByName(tagName);
|
||||
let r = parseInt(color.substr(1, 2), 16);
|
||||
let g = parseInt(color.substr(3, 2), 16);
|
||||
let b = parseInt(color.substr(5, 2), 16);
|
||||
let brightness = Math.round(((r * 299) + (g * 587) + (b * 114)) / 1000);
|
||||
return brightness > 125 ? "#000000" : "#ffffff";
|
||||
}
|
||||
|
||||
// Populate the tag filter dropdown
|
||||
function populateTagFilterDropdown(data) {
|
||||
let tags = new Set();
|
||||
data.forEach(subd => {
|
||||
subd.Tags.forEach(tag => tags.add(tag));
|
||||
});
|
||||
tags = Array.from(tags).sort((a, b) => a.localeCompare(b));
|
||||
let dropdownMenu = $("#tagFilterDropdown .tagList");
|
||||
dropdownMenu.html(`<div class="item tag-select" data-value="">
|
||||
<div class="ui grey empty circular label"></div>
|
||||
Show all
|
||||
</div>`);
|
||||
tags.forEach(tag => {
|
||||
let thisTagColor = getTagColorByName(tag);
|
||||
dropdownMenu.append(`<div class="item tag-select" data-value="${tag}">
|
||||
<div class="ui empty circular label" style="background-color: ${thisTagColor}; border-color: ${thisTagColor};" ></div>
|
||||
${tag}
|
||||
</div>`);
|
||||
});
|
||||
}
|
||||
|
||||
// Edit tags for a specific endpoint
|
||||
function editTags(uuid){
|
||||
let payload = encodeURIComponent(JSON.stringify({
|
||||
ept: "host",
|
||||
ep: uuid
|
||||
}));
|
||||
showSideWrapper("snippet/tagEditor.html?t=" + Date.now() + "#" + payload);
|
||||
}
|
||||
|
||||
// Render the tags preview from tag editing snippet
|
||||
function renderTagsPreview(endpoint, tags){
|
||||
let targetProxyRuleEle = $(".subdEntry[eptuuid='" + endpoint + "'] td[data-label='tags']");
|
||||
//Update the tag DOM
|
||||
let newTagDOM = tags.map(tag => `<span class="ui tiny label tag-select" style="background-color: ${getTagColorByName(tag)}; color: ${getTagTextColor(tag)}">${tag}</span>`).join("");
|
||||
$(targetProxyRuleEle).find(".tags-list").html(newTagDOM);
|
||||
|
||||
//Update the tag payload
|
||||
$(targetProxyRuleEle).attr("payload", encodeURIComponent(JSON.stringify(tags)));
|
||||
}
|
||||
|
||||
function getTagsArrayFromEndpoint(endpoint){
|
||||
let targetProxyRuleEle = $(".subdEntry[eptuuid='" + endpoint + "'] td[data-label='tags']");
|
||||
let tags = $(targetProxyRuleEle).attr("payload");
|
||||
return JSON.parse(decodeURIComponent(tags));
|
||||
}
|
||||
|
||||
// Initialize the proxy list on page load
|
||||
$(document).ready(function() {
|
||||
listProxyEndpoints();
|
||||
|
||||
// Event listener for clicking on tags
|
||||
$(document).on('click', '.tag-select', function() {
|
||||
let tag = $(this).text().trim();
|
||||
$('#tagFilterDropdown').dropdown('set selected', tag);
|
||||
filterProxyList();
|
||||
});
|
||||
});
|
||||
</script>
|
1455
src/web/components/httprp_new.html
Normal file
1455
src/web/components/httprp_new.html
Normal file
File diff suppressed because it is too large
Load Diff
@@ -25,6 +25,7 @@
|
||||
<ul>
|
||||
<li><a href="https://www.authelia.com" rel=”noopener noreferrer” target="_blank">Authelia</a></li>
|
||||
<li><a href="https://goauthentik.io/" rel=”noopener noreferrer” target="_blank">Authentik</a></li>
|
||||
<li><a href="https://oauth2-proxy.github.io/oauth2-proxy/" rel=”noopener noreferrer” target="_blank">OAuth2 Proxy</a></li>
|
||||
</ul>
|
||||
<form class="ui form" action="#" id="forwardAuthSettings">
|
||||
<div class="field">
|
||||
@@ -42,26 +43,43 @@
|
||||
<div class="field">
|
||||
<label for="forwardAuthResponseHeaders">Response Headers</label>
|
||||
<input type="text" id="forwardAuthResponseHeaders" name="forwardAuthResponseHeaders" placeholder="Enter Forward Auth Response Headers">
|
||||
<small>Comma separated list of case-insensitive headers to copy from the authorization servers response to the request sent to the backend. If not set no headers are copied. <br>
|
||||
<strong>Example:</strong> <code>Remote-User,Remote-Groups,Remote-Email,Remote-Name</code></small>
|
||||
<small>
|
||||
Comma separated list of case-insensitive headers to copy from the authorization servers response to the request sent to the backend. If not set no headers are copied. <br>
|
||||
<strong>Example:</strong> <code>Remote-User,Remote-Groups,Remote-Email,Remote-Name</code>
|
||||
</small>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="forwardAuthResponseClientHeaders">Response Client Headers</label>
|
||||
<input type="text" id="forwardAuthResponseClientHeaders" name="forwardAuthResponseClientHeaders" placeholder="Enter Forward Auth Response Client Headers">
|
||||
<small>Comma separated list of case-insensitive headers to copy from the authorization servers response to the response sent to the client. If not set no headers are copied. <br>
|
||||
<strong>Example:</strong> <code>Set-Cookie,WWW-Authenticate</code></small>
|
||||
<small>
|
||||
Comma separated list of case-insensitive headers to copy from the authorization servers response to the <b><i>response sent to the client</i></b>. If not set no headers are copied. <br>
|
||||
<strong>Example:</strong> <code>Set-Cookie,WWW-Authenticate</code>
|
||||
</small>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="forwardAuthRequestHeaders">Request Headers</label>
|
||||
<input type="text" id="forwardAuthRequestHeaders" name="forwardAuthRequestHeaders" placeholder="Enter Forward Auth Request Headers">
|
||||
<small>Comma separated list of case-insensitive headers to copy from the original request to the request made to the authorization server. If not set all headers are copied. <br>
|
||||
<strong>Example:</strong> <code>Cookie,Authorization</code></small>
|
||||
<small>
|
||||
Comma separated list of case-insensitive headers to copy from the original request to the <b><i>request made to the authorization server</i></b>. If not set all headers are copied. <br>
|
||||
<strong>Recommendation:</strong> Generally it's recommended to leave this blank or use the below example for predictable results. <br>
|
||||
<strong>Example:</strong> <code>Accept,X-Requested-With,Cookie,Authorization,Proxy-Authorization</code>
|
||||
</small>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="forwardAuthRequestIncludedCookies">Request Included Cookies</label>
|
||||
<input type="text" id="forwardAuthRequestIncludedCookies" name="forwardAuthRequestIncludedCookies" placeholder="Enter Forward Auth Request Included Cookies">
|
||||
<small>
|
||||
Comma separated list of case-sensitive cookie names to copy from the original request to the <b><i>request made to the authorization server</i></b>. If not set all cookies are included. This allows omitting all cookies not required by the authorization server.<br>
|
||||
<strong>Example:</strong> <code>authelia_session,another_session</code>
|
||||
</small>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="forwardAuthRequestExcludedCookies">Request Excluded Cookies</label>
|
||||
<input type="text" id="forwardAuthRequestExcludedCookies" name="forwardAuthRequestExcludedCookies" placeholder="Enter Forward Auth Request Excluded Cookies">
|
||||
<small>Comma separated list of case-sensitive cookie names to exclude from the request to the backend. If not set no cookies are excluded. <br>
|
||||
<strong>Example:</strong> <code>authelia_session,another_session</code></small>
|
||||
<small>
|
||||
Comma separated list of case-sensitive cookie names to exclude from the <b><i>request made to the backend application</i></b>. If not set no cookies are excluded. This allows omitting the cookie intended only for the authorization server.<br>
|
||||
<strong>Example:</strong> <code>authelia_session,another_session</code>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -132,6 +150,7 @@
|
||||
$('#forwardAuthResponseHeaders').val(data.responseHeaders.join(","));
|
||||
$('#forwardAuthResponseClientHeaders').val(data.responseClientHeaders.join(","));
|
||||
$('#forwardAuthRequestHeaders').val(data.requestHeaders.join(","));
|
||||
$('#forwardAuthRequestIncludedCookies').val(data.requestIncludedCookies.join(","));
|
||||
$('#forwardAuthRequestExcludedCookies').val(data.requestExcludedCookies.join(","));
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
@@ -170,6 +189,7 @@
|
||||
const responseHeaders = $('#forwardAuthResponseHeaders').val();
|
||||
const responseClientHeaders = $('#forwardAuthResponseClientHeaders').val();
|
||||
const requestHeaders = $('#forwardAuthRequestHeaders').val();
|
||||
const requestIncludedCookies = $('#forwardAuthRequestIncludedCookies').val();
|
||||
const requestExcludedCookies = $('#forwardAuthRequestExcludedCookies').val();
|
||||
|
||||
console.log(`Updating Forward Auth settings. Address: ${address}. Response Headers: ${responseHeaders}. Response Client Headers: ${responseClientHeaders}. Request Headers: ${requestHeaders}. Request Excluded Cookies: ${requestExcludedCookies}.`);
|
||||
|
@@ -29,6 +29,13 @@
|
||||
<small>If this folder do not contains any index files, list the directory of this folder.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline field">
|
||||
<div class="ui toggle checkbox">
|
||||
<input id="webserv_enableAllInterfaces" type="checkbox" class="hidden">
|
||||
<label>Listening to All Interfaces</label>
|
||||
<small>When disabled, the web server will only listen to localhost (127.0.0.1) and only reachable via reverse proxy rules.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Document Root Folder</label>
|
||||
<input id="webserv_docRoot" type="text" readonly="true">
|
||||
@@ -136,6 +143,13 @@
|
||||
$("#webserv_dirManager").remove();
|
||||
}
|
||||
|
||||
if (!data.DisableListenToAllInterface){
|
||||
//Options on UI is flipped
|
||||
$("#webserv_enableAllInterfaces").parent().checkbox("set checked");
|
||||
}else{
|
||||
$("#webserv_enableAllInterfaces").parent().checkbox("set unchecked");
|
||||
}
|
||||
|
||||
$("#webserv_listenPort").val(data.ListeningPort);
|
||||
updateWebServLinkExample(data.ListeningPort);
|
||||
|
||||
@@ -178,6 +192,23 @@
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
$("#webserv_enableAllInterfaces").off("change").on("change", function(){
|
||||
let disable = !$(this)[0].checked;
|
||||
$.cjax({
|
||||
url: "/api/webserv/disableListenAllInterface",
|
||||
method: "POST",
|
||||
data: {"disable": disable},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false);
|
||||
}else{
|
||||
msgbox("Listening interface setting updated");
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
$("#webserv_listenPort").off("change").on("change", function(){
|
||||
let newPort = $(this).val();
|
||||
|
||||
|
@@ -22,7 +22,7 @@
|
||||
<br>
|
||||
<div class="ui container">
|
||||
<p>Tags currently applied to this host name / proxy rule</p>
|
||||
<div style="max-height: 300px; overflow-y: scroll;">
|
||||
<div>
|
||||
<table class="ui compact basic unstackable celled table">
|
||||
<thead>
|
||||
<tr>
|
||||
|
Reference in New Issue
Block a user