mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-08-14 17:09:24 +02:00
Compare commits
32 Commits
475650de0d
...
v3.2.4
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ffd3909964 | ||
![]() |
3ddccdffce | ||
![]() |
929d4cc82a | ||
![]() |
4f1cd8a571 | ||
![]() |
f6b3656bb1 | ||
![]() |
74a816216e | ||
![]() |
4a093cf096 | ||
![]() |
68f9fccf3a | ||
![]() |
f276040ad0 | ||
![]() |
2f40593daf | ||
![]() |
0b6dbd49bb | ||
![]() |
eb07917c14 | ||
![]() |
217bc48001 | ||
![]() |
38cfab4a09 | ||
![]() |
217e5e90ff | ||
![]() |
4a37a989a0 | ||
![]() |
eb540b774d | ||
![]() |
26d03f9ad4 | ||
![]() |
31ba4f20ae | ||
![]() |
650d61ba24 | ||
![]() |
6d0c0be8c2 | ||
![]() |
366a44a992 | ||
![]() |
7164b74d4a | ||
![]() |
b01a21f318 | ||
![]() |
809e1fa815 | ||
![]() |
c7b5e0994e | ||
![]() |
1f8684481a | ||
![]() |
0e74ff69c3 | ||
![]() |
f0fa71c5b4 | ||
![]() |
8cb47e19fa | ||
![]() |
e2882b6436 | ||
![]() |
61b873451f |
17
.github/workflows/docker.yml
vendored
17
.github/workflows/docker.yml
vendored
@@ -2,7 +2,7 @@ name: Build and push Docker image
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
release:
|
release:
|
||||||
types: [ published ]
|
types: [ released, prereleased ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
setup-build-push:
|
setup-build-push:
|
||||||
@@ -33,7 +33,8 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cp -lr $GITHUB_WORKSPACE/src/ $GITHUB_WORKSPACE/docker/src/
|
cp -lr $GITHUB_WORKSPACE/src/ $GITHUB_WORKSPACE/docker/src/
|
||||||
|
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image (Release)
|
||||||
|
if: "!github.event.release.prerelease"
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: ./docker
|
context: ./docker
|
||||||
@@ -45,3 +46,15 @@ jobs:
|
|||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
|
|
||||||
|
- name: Build and push Docker image (Prerelease)
|
||||||
|
if: "github.event.release.prerelease"
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: ./docker
|
||||||
|
push: true
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
tags: |
|
||||||
|
zoraxydocker/zoraxy:${{ github.event.release.tag_name }}
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
|
||||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@@ -50,4 +50,10 @@ src/log/
|
|||||||
example/plugins/ztnc/ztnc.db
|
example/plugins/ztnc/ztnc.db
|
||||||
example/plugins/ztnc/authtoken.secret
|
example/plugins/ztnc/authtoken.secret
|
||||||
example/plugins/ztnc/ztnc.db.lock
|
example/plugins/ztnc/ztnc.db.lock
|
||||||
|
.idea
|
||||||
|
conf
|
||||||
|
log
|
||||||
|
tmp
|
||||||
|
sys.*
|
||||||
|
www/html/index.html
|
||||||
*.exe
|
*.exe
|
@@ -519,8 +519,8 @@
|
|||||||
</div>
|
</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/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="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="https://github.com/aroz-online/zoraxy-official-plugins" 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://zoraxy.aroz.org/plugins/html/" target="_blank">Plugin Development Guide</a></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="three wide column">
|
<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/*`:
|
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] 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] 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] Method: GET
|
||||||
|
@@ -615,7 +615,7 @@ func RenderDebugUI(w http.ResponseWriter, r *http.Request) {
|
|||||||
:
|
:
|
||||||
</p>
|
</p>
|
||||||
</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] 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] Method: GET
|
||||||
[2025-05-30 20:44:26.142645] [plugin-manager] [system:info] [Dynamic Capture Example:22964] Hostname: a.localhost
|
[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] 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] ProtoMajor: 2
|
||||||
[2025-05-30 20:44:26.143165] [plugin-manager] [system:info] [Dynamic Capture Example:22964] ProtoMinor: 0
|
[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>
|
<div class="ts-divider has-top-spaced-large"></div>
|
||||||
<p>
|
<p>
|
||||||
<p class="ts-text">
|
<p class="ts-text">
|
||||||
|
@@ -34,6 +34,7 @@ func RegisterHTTPProxyAPIs(authRouter *auth.RouterDef) {
|
|||||||
authRouter.HandleFunc("/api/proxy/detail", ReverseProxyListDetail)
|
authRouter.HandleFunc("/api/proxy/detail", ReverseProxyListDetail)
|
||||||
authRouter.HandleFunc("/api/proxy/edit", ReverseProxyHandleEditEndpoint)
|
authRouter.HandleFunc("/api/proxy/edit", ReverseProxyHandleEditEndpoint)
|
||||||
authRouter.HandleFunc("/api/proxy/setAlias", ReverseProxyHandleAlias)
|
authRouter.HandleFunc("/api/proxy/setAlias", ReverseProxyHandleAlias)
|
||||||
|
authRouter.HandleFunc("/api/proxy/setHostname", ReverseProxyHandleSetHostname)
|
||||||
authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint)
|
authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint)
|
||||||
authRouter.HandleFunc("/api/proxy/updateCredentials", UpdateProxyBasicAuthCredentials)
|
authRouter.HandleFunc("/api/proxy/updateCredentials", UpdateProxyBasicAuthCredentials)
|
||||||
authRouter.HandleFunc("/api/proxy/tlscheck", domainsniff.HandleCheckSiteSupportTLS)
|
authRouter.HandleFunc("/api/proxy/tlscheck", domainsniff.HandleCheckSiteSupportTLS)
|
||||||
@@ -83,6 +84,7 @@ func RegisterTLSAPIs(authRouter *auth.RouterDef) {
|
|||||||
// Register the APIs for Authentication handlers like Forward Auth and OAUTH2
|
// Register the APIs for Authentication handlers like Forward Auth and OAUTH2
|
||||||
func RegisterAuthenticationHandlerAPIs(authRouter *auth.RouterDef) {
|
func RegisterAuthenticationHandlerAPIs(authRouter *auth.RouterDef) {
|
||||||
authRouter.HandleFunc("/api/sso/forward-auth", forwardAuthRouter.HandleAPIOptions)
|
authRouter.HandleFunc("/api/sso/forward-auth", forwardAuthRouter.HandleAPIOptions)
|
||||||
|
authRouter.HandleFunc("/api/sso/OAuth2", oauth2Router.HandleSetOAuth2Settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register the APIs for redirection rules management functions
|
// Register the APIs for redirection rules management functions
|
||||||
@@ -191,6 +193,7 @@ func RegisterStaticWebServerAPIs(authRouter *auth.RouterDef) {
|
|||||||
authRouter.HandleFunc("/api/webserv/stop", staticWebServer.HandleStopServer)
|
authRouter.HandleFunc("/api/webserv/stop", staticWebServer.HandleStopServer)
|
||||||
authRouter.HandleFunc("/api/webserv/setPort", HandleStaticWebServerPortChange)
|
authRouter.HandleFunc("/api/webserv/setPort", HandleStaticWebServerPortChange)
|
||||||
authRouter.HandleFunc("/api/webserv/setDirList", staticWebServer.SetEnableDirectoryListing)
|
authRouter.HandleFunc("/api/webserv/setDirList", staticWebServer.SetEnableDirectoryListing)
|
||||||
|
authRouter.HandleFunc("/api/webserv/disableListenAllInterface", staticWebServer.SetDisableListenToAllInterface)
|
||||||
/* File Manager */
|
/* File Manager */
|
||||||
if *allowWebFileManager {
|
if *allowWebFileManager {
|
||||||
authRouter.HandleFunc("/api/fs/list", staticWebServer.FileManager.HandleList)
|
authRouter.HandleFunc("/api/fs/list", staticWebServer.FileManager.HandleList)
|
||||||
|
@@ -13,6 +13,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/auth/sso/oauth2"
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/access"
|
"imuslab.com/zoraxy/mod/access"
|
||||||
"imuslab.com/zoraxy/mod/acme"
|
"imuslab.com/zoraxy/mod/acme"
|
||||||
"imuslab.com/zoraxy/mod/auth"
|
"imuslab.com/zoraxy/mod/auth"
|
||||||
@@ -42,7 +44,7 @@ import (
|
|||||||
const (
|
const (
|
||||||
/* Build Constants */
|
/* Build Constants */
|
||||||
SYSTEM_NAME = "Zoraxy"
|
SYSTEM_NAME = "Zoraxy"
|
||||||
SYSTEM_VERSION = "3.2.2"
|
SYSTEM_VERSION = "3.2.4"
|
||||||
DEVELOPMENT_BUILD = false
|
DEVELOPMENT_BUILD = false
|
||||||
|
|
||||||
/* System Constants */
|
/* System Constants */
|
||||||
@@ -143,7 +145,8 @@ var (
|
|||||||
pluginManager *plugins.Manager //Plugin manager for managing plugins
|
pluginManager *plugins.Manager //Plugin manager for managing plugins
|
||||||
|
|
||||||
//Authentication Provider
|
//Authentication Provider
|
||||||
forwardAuthRouter *forward.AuthRouter // Forward Auth router for Authelia/Authentik/etc authentication
|
forwardAuthRouter *forward.AuthRouter // Forward Auth router for Authelia/Authentik/etc authentication
|
||||||
|
oauth2Router *oauth2.OAuth2Router //OAuth2Router router for OAuth2Router authentication
|
||||||
|
|
||||||
//Helper modules
|
//Helper modules
|
||||||
EmailSender *email.Sender //Email sender that handle email sending
|
EmailSender *email.Sender //Email sender that handle email sending
|
||||||
|
41
src/go.mod
41
src/go.mod
@@ -16,35 +16,18 @@ require (
|
|||||||
github.com/grandcat/zeroconf v1.0.0
|
github.com/grandcat/zeroconf v1.0.0
|
||||||
github.com/likexian/whois v1.15.1
|
github.com/likexian/whois v1.15.1
|
||||||
github.com/microcosm-cc/bluemonday v1.0.26
|
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/shirou/gopsutil/v4 v4.25.1
|
||||||
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/syndtr/goleveldb v1.0.0
|
github.com/syndtr/goleveldb v1.0.0
|
||||||
golang.org/x/net v0.33.0
|
golang.org/x/net v0.33.0
|
||||||
|
golang.org/x/oauth2 v0.24.0
|
||||||
golang.org/x/text v0.21.0
|
golang.org/x/text v0.21.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go/auth v0.13.0 // indirect
|
cloud.google.com/go/auth v0.13.0 // indirect
|
||||||
cloud.google.com/go/auth/oauth2adapt v0.2.6 // 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/monperrus/crawler-user-agents v1.1.0 // 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
|
cloud.google.com/go/compute/metadata v0.6.0 // indirect
|
||||||
github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect
|
github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect
|
||||||
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // 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/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/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/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 v14.2.0+incompatible // indirect
|
||||||
github.com/Azure/go-autorest/autorest v0.11.29 // indirect
|
github.com/Azure/go-autorest/autorest v0.11.29 // indirect
|
||||||
github.com/Azure/go-autorest/autorest/adal v0.9.22 // 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/aws-sdk-go-v2/service/sts v1.33.3 // indirect
|
||||||
github.com/aws/smithy-go v1.22.1 // indirect
|
github.com/aws/smithy-go v1.22.1 // indirect
|
||||||
github.com/aymerick/douceur v0.2.0 // 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/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||||
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
|
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // 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/dnsimple/dnsimple-go v1.7.0 // indirect
|
||||||
github.com/docker/go-connections v0.5.0 // indirect
|
github.com/docker/go-connections v0.5.0 // indirect
|
||||||
github.com/docker/go-units 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/fatih/structs v1.1.0 // indirect
|
||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.8.0 // 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-jose/go-jose/v4 v4.0.4 // indirect
|
||||||
github.com/go-logr/logr v1.4.2 // indirect
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
github.com/go-logr/stdr v1.2.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-resty/resty/v2 v2.16.2 // indirect
|
||||||
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
||||||
github.com/goccy/go-json v0.10.4 // indirect
|
github.com/goccy/go-json v0.10.4 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.1 // 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/go-querystring v1.1.0 // indirect
|
||||||
github.com/google/s2a-go v0.1.8 // indirect
|
github.com/google/s2a-go v0.1.8 // indirect
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // 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-cleanhttp v0.5.2 // indirect
|
||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.7 // 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/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect
|
||||||
github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect
|
github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect
|
||||||
github.com/jmespath/go-jmespath v0.4.0 // 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/nzdjb/go-metaname v1.0.0 // indirect
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
github.com/opencontainers/image-spec v1.1.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/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/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // 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/pquerna/otp v1.4.0 // indirect
|
||||||
github.com/sacloud/api-client-go v0.2.10 // indirect
|
github.com/sacloud/api-client-go v0.2.10 // indirect
|
||||||
github.com/sacloud/go-http v0.1.8 // indirect
|
github.com/sacloud/go-http v0.1.8 // indirect
|
||||||
github.com/sacloud/iaas-api-go v1.14.0 // indirect
|
github.com/sacloud/iaas-api-go v1.14.0 // indirect
|
||||||
github.com/sacloud/packages-go v0.0.10 // indirect
|
github.com/sacloud/packages-go v0.0.10 // indirect
|
||||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30 // 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/sirupsen/logrus v1.9.3 // indirect
|
||||||
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // 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/softlayer-go v1.1.7 // indirect
|
||||||
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
|
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
|
||||||
github.com/spf13/cast v1.6.0 // 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/common v1.0.1065 // indirect
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod 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/transip/gotransip/v6 v6.26.0 // indirect
|
||||||
github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec // indirect
|
github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec // indirect
|
||||||
github.com/vinyldns/go-vinyldns v0.9.16 // 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-genproto v0.0.0-20241220122821-aeb3b05efd1c // indirect
|
||||||
github.com/yandex-cloud/go-sdk v0.0.0-20241220131134-2393e243c134 // 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/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect
|
||||||
go.opentelemetry.io/otel v1.29.0 // indirect
|
go.opentelemetry.io/otel v1.29.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.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
|
go.uber.org/ratelimit v0.3.0 // indirect
|
||||||
golang.org/x/crypto v0.31.0 // indirect
|
golang.org/x/crypto v0.31.0 // indirect
|
||||||
golang.org/x/mod v0.22.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/sync v0.10.0 // indirect
|
||||||
|
golang.org/x/sys v0.28.0 // indirect
|
||||||
golang.org/x/time v0.8.0 // indirect
|
golang.org/x/time v0.8.0 // indirect
|
||||||
golang.org/x/tools v0.28.0 // indirect
|
golang.org/x/tools v0.28.0 // indirect
|
||||||
google.golang.org/api v0.214.0 // indirect
|
google.golang.org/api v0.214.0 // indirect
|
||||||
|
@@ -37,6 +37,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -125,7 +126,13 @@ func main() {
|
|||||||
//Start the finalize sequences
|
//Start the finalize sequences
|
||||||
finalSequence()
|
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))
|
err = http.ListenAndServe(*webUIPort, csrfMiddleware(webminPanelMux))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -56,7 +56,7 @@ func (ar *AuthentikRouter) HandleSetAuthentikURLAndHTTPS(w http.ResponseWriter,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
useHTTPS, err := utils.PostBool(r, "useHTTPS")
|
useHTTPS, err := utils.PostBool(r, "authentikUseHttps")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
useHTTPS = false
|
useHTTPS = false
|
||||||
}
|
}
|
||||||
|
@@ -11,6 +11,7 @@ const (
|
|||||||
DatabaseKeyResponseHeaders = "responseHeaders"
|
DatabaseKeyResponseHeaders = "responseHeaders"
|
||||||
DatabaseKeyResponseClientHeaders = "responseClientHeaders"
|
DatabaseKeyResponseClientHeaders = "responseClientHeaders"
|
||||||
DatabaseKeyRequestHeaders = "requestHeaders"
|
DatabaseKeyRequestHeaders = "requestHeaders"
|
||||||
|
DatabaseKeyRequestIncludedCookies = "requestIncludedCookies"
|
||||||
DatabaseKeyRequestExcludedCookies = "requestExcludedCookies"
|
DatabaseKeyRequestExcludedCookies = "requestExcludedCookies"
|
||||||
|
|
||||||
HeaderXForwardedProto = "X-Forwarded-Proto"
|
HeaderXForwardedProto = "X-Forwarded-Proto"
|
||||||
|
@@ -3,7 +3,6 @@ package forward
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -28,6 +27,10 @@ type AuthRouterOptions struct {
|
|||||||
// headers are copied.
|
// headers are copied.
|
||||||
RequestHeaders []string
|
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 is a list of cookie keys that should be removed from every request sent to the upstream.
|
||||||
RequestExcludedCookies []string
|
RequestExcludedCookies []string
|
||||||
|
|
||||||
@@ -47,16 +50,18 @@ func NewAuthRouter(options *AuthRouterOptions) *AuthRouter {
|
|||||||
//Read settings from database if available.
|
//Read settings from database if available.
|
||||||
options.Database.Read(DatabaseTable, DatabaseKeyAddress, &options.Address)
|
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, DatabaseKeyResponseHeaders, &responseHeaders)
|
||||||
options.Database.Read(DatabaseTable, DatabaseKeyResponseClientHeaders, &responseClientHeaders)
|
options.Database.Read(DatabaseTable, DatabaseKeyResponseClientHeaders, &responseClientHeaders)
|
||||||
options.Database.Read(DatabaseTable, DatabaseKeyRequestHeaders, &requestHeaders)
|
options.Database.Read(DatabaseTable, DatabaseKeyRequestHeaders, &requestHeaders)
|
||||||
|
options.Database.Read(DatabaseTable, DatabaseKeyRequestIncludedCookies, &requestIncludedCookies)
|
||||||
options.Database.Read(DatabaseTable, DatabaseKeyRequestExcludedCookies, &requestExcludedCookies)
|
options.Database.Read(DatabaseTable, DatabaseKeyRequestExcludedCookies, &requestExcludedCookies)
|
||||||
|
|
||||||
options.ResponseHeaders = strings.Split(responseHeaders, ",")
|
options.ResponseHeaders = strings.Split(responseHeaders, ",")
|
||||||
options.ResponseClientHeaders = strings.Split(responseClientHeaders, ",")
|
options.ResponseClientHeaders = strings.Split(responseClientHeaders, ",")
|
||||||
options.RequestHeaders = strings.Split(requestHeaders, ",")
|
options.RequestHeaders = strings.Split(requestHeaders, ",")
|
||||||
|
options.RequestIncludedCookies = strings.Split(requestIncludedCookies, ",")
|
||||||
options.RequestExcludedCookies = strings.Split(requestExcludedCookies, ",")
|
options.RequestExcludedCookies = strings.Split(requestExcludedCookies, ",")
|
||||||
|
|
||||||
return &AuthRouter{
|
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) {
|
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,
|
DatabaseKeyAddress: ar.options.Address,
|
||||||
DatabaseKeyResponseHeaders: ar.options.ResponseHeaders,
|
DatabaseKeyResponseHeaders: ar.options.ResponseHeaders,
|
||||||
DatabaseKeyResponseClientHeaders: ar.options.ResponseClientHeaders,
|
DatabaseKeyResponseClientHeaders: ar.options.ResponseClientHeaders,
|
||||||
DatabaseKeyRequestHeaders: ar.options.RequestHeaders,
|
DatabaseKeyRequestHeaders: ar.options.RequestHeaders,
|
||||||
|
DatabaseKeyRequestIncludedCookies: ar.options.RequestIncludedCookies,
|
||||||
DatabaseKeyRequestExcludedCookies: ar.options.RequestExcludedCookies,
|
DatabaseKeyRequestExcludedCookies: ar.options.RequestExcludedCookies,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -108,6 +114,7 @@ func (ar *AuthRouter) handleOptionsPOST(w http.ResponseWriter, r *http.Request)
|
|||||||
responseHeaders, _ := utils.PostPara(r, DatabaseKeyResponseHeaders)
|
responseHeaders, _ := utils.PostPara(r, DatabaseKeyResponseHeaders)
|
||||||
responseClientHeaders, _ := utils.PostPara(r, DatabaseKeyResponseClientHeaders)
|
responseClientHeaders, _ := utils.PostPara(r, DatabaseKeyResponseClientHeaders)
|
||||||
requestHeaders, _ := utils.PostPara(r, DatabaseKeyRequestHeaders)
|
requestHeaders, _ := utils.PostPara(r, DatabaseKeyRequestHeaders)
|
||||||
|
requestIncludedCookies, _ := utils.PostPara(r, DatabaseKeyRequestIncludedCookies)
|
||||||
requestExcludedCookies, _ := utils.PostPara(r, DatabaseKeyRequestExcludedCookies)
|
requestExcludedCookies, _ := utils.PostPara(r, DatabaseKeyRequestExcludedCookies)
|
||||||
|
|
||||||
// Write changes to runtime
|
// 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.ResponseHeaders = strings.Split(responseHeaders, ",")
|
||||||
ar.options.ResponseClientHeaders = strings.Split(responseClientHeaders, ",")
|
ar.options.ResponseClientHeaders = strings.Split(responseClientHeaders, ",")
|
||||||
ar.options.RequestHeaders = strings.Split(requestHeaders, ",")
|
ar.options.RequestHeaders = strings.Split(requestHeaders, ",")
|
||||||
|
ar.options.RequestIncludedCookies = strings.Split(requestIncludedCookies, ",")
|
||||||
ar.options.RequestExcludedCookies = strings.Split(requestExcludedCookies, ",")
|
ar.options.RequestExcludedCookies = strings.Split(requestExcludedCookies, ",")
|
||||||
|
|
||||||
// Write changes to database
|
// 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, DatabaseKeyResponseHeaders, responseHeaders)
|
||||||
ar.options.Database.Write(DatabaseTable, DatabaseKeyResponseClientHeaders, responseClientHeaders)
|
ar.options.Database.Write(DatabaseTable, DatabaseKeyResponseClientHeaders, responseClientHeaders)
|
||||||
ar.options.Database.Write(DatabaseTable, DatabaseKeyRequestHeaders, requestHeaders)
|
ar.options.Database.Write(DatabaseTable, DatabaseKeyRequestHeaders, requestHeaders)
|
||||||
|
ar.options.Database.Write(DatabaseTable, DatabaseKeyRequestIncludedCookies, requestIncludedCookies)
|
||||||
ar.options.Database.Write(DatabaseTable, DatabaseKeyRequestExcludedCookies, requestExcludedCookies)
|
ar.options.Database.Write(DatabaseTable, DatabaseKeyRequestExcludedCookies, requestExcludedCookies)
|
||||||
|
|
||||||
utils.SendOK(w)
|
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.
|
// HandleAuthProviderRouting is the internal handler for Forward Auth authentication.
|
||||||
func (ar *AuthRouter) HandleAuthProviderRouting(w http.ResponseWriter, r *http.Request) error {
|
func (ar *AuthRouter) HandleAuthProviderRouting(w http.ResponseWriter, r *http.Request) error {
|
||||||
if ar.options.Address == "" {
|
if ar.options.Address == "" {
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
return ar.handle500Error(w, nil, "Address not set")
|
||||||
|
|
||||||
ar.options.Logger.PrintAndLog(LogTitle, "Address not set", nil)
|
|
||||||
|
|
||||||
return ErrInternalServerError
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make a request to Authz Server to verify the request
|
// 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)
|
req, err := http.NewRequest(http.MethodGet, ar.options.Address, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
return ar.handle500Error(w, err, "Unable to create request")
|
||||||
|
|
||||||
ar.options.Logger.PrintAndLog(LogTitle, "Unable to create request", err)
|
|
||||||
|
|
||||||
return ErrInternalServerError
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add opt-in support for copying the request body to the forward auth request.
|
|
||||||
headerCopyIncluded(r.Header, req.Header, ar.options.RequestHeaders, true)
|
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)
|
rSetForwardedHeaders(r, req)
|
||||||
|
|
||||||
// Make the Authz Request.
|
// Make the Authz Request.
|
||||||
respForwarded, err := ar.client.Do(req)
|
respForwarded, err := ar.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
return ar.handle500Error(w, err, "Unable to perform forwarded auth due to a request error")
|
||||||
|
|
||||||
ar.options.Logger.PrintAndLog(LogTitle, "Unable to perform forwarded auth due to a request error", err)
|
|
||||||
|
|
||||||
return ErrInternalServerError
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defer respForwarded.Body.Close()
|
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.
|
// 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 respForwarded.StatusCode >= http.StatusOK && respForwarded.StatusCode < http.StatusMultipleChoices {
|
||||||
if len(ar.options.ResponseClientHeaders) != 0 {
|
if len(ar.options.ResponseClientHeaders) != 0 {
|
||||||
headerCopyIncluded(respForwarded.Header, w.Header(), ar.options.ResponseClientHeaders, false)
|
headerCopyIncluded(respForwarded.Header, w.Header(), ar.options.ResponseClientHeaders, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ar.options.RequestExcludedCookies) != 0 {
|
headerCookieRedact(r, ar.options.RequestExcludedCookies, true)
|
||||||
// If the user has specified a list of cookies to be removed from the request, deterministically remove them.
|
|
||||||
headerCookieRedact(r, ar.options.RequestExcludedCookies)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(ar.options.ResponseHeaders) != 0 {
|
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
|
// 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)
|
headerCopyIncluded(respForwarded.Header, w.Header(), ar.options.ResponseHeaders, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return the request to the proxy for forwarding to the backend.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy the response.
|
// Copy the unsuccessful response.
|
||||||
headerCopyExcluded(respForwarded.Header, w.Header(), nil)
|
headerCopyExcluded(respForwarded.Header, w.Header(), nil)
|
||||||
|
|
||||||
w.WriteHeader(respForwarded.StatusCode)
|
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 {
|
if _, err = w.Write(body); err != nil {
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
return ar.handle500Error(w, err, "Unable to write response")
|
||||||
|
|
||||||
ar.options.Logger.PrintAndLog(LogTitle, "Unable to write response", err)
|
|
||||||
|
|
||||||
return ErrInternalServerError
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ErrUnauthorized
|
return ErrUnauthorized
|
||||||
}
|
}
|
||||||
|
|
||||||
func scheme(r *http.Request) string {
|
// handle500Error is func intended on factorizing a commonly repeated functional flow within this provider.
|
||||||
if r.TLS != nil {
|
func (ar *AuthRouter) handle500Error(w http.ResponseWriter, err error, message string) error {
|
||||||
return "https"
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
}
|
|
||||||
|
|
||||||
return "http"
|
ar.options.Logger.PrintAndLog(LogTitle, message, err)
|
||||||
}
|
|
||||||
|
return ErrInternalServerError
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
299
src/mod/auth/sso/oauth2/oauth2.go
Normal file
299
src/mod/auth/sso/oauth2/oauth2.go
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
package oauth2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"imuslab.com/zoraxy/mod/database"
|
||||||
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OAuth2RouterOptions struct {
|
||||||
|
OAuth2ServerURL string //The URL of the OAuth 2.0 server server
|
||||||
|
OAuth2TokenURL string //The URL of the OAuth 2.0 token server
|
||||||
|
OAuth2ClientId string //The client id for OAuth 2.0 Application
|
||||||
|
OAuth2ClientSecret string //The client secret for OAuth 2.0 Application
|
||||||
|
OAuth2WellKnownUrl string //The well-known url for OAuth 2.0 server
|
||||||
|
OAuth2UserInfoUrl string //The URL of the OAuth 2.0 user info endpoint
|
||||||
|
OAuth2Scopes string //The scopes for OAuth 2.0 Application
|
||||||
|
Logger *logger.Logger
|
||||||
|
Database *database.Database
|
||||||
|
}
|
||||||
|
|
||||||
|
type OIDCDiscoveryDocument struct {
|
||||||
|
AuthorizationEndpoint string `json:"authorization_endpoint"`
|
||||||
|
ClaimsSupported []string `json:"claims_supported"`
|
||||||
|
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"`
|
||||||
|
GrantTypesSupported []string `json:"grant_types_supported"`
|
||||||
|
IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported"`
|
||||||
|
Issuer string `json:"issuer"`
|
||||||
|
JwksURI string `json:"jwks_uri"`
|
||||||
|
ResponseTypesSupported []string `json:"response_types_supported"`
|
||||||
|
ScopesSupported []string `json:"scopes_supported"`
|
||||||
|
SubjectTypesSupported []string `json:"subject_types_supported"`
|
||||||
|
TokenEndpoint string `json:"token_endpoint"`
|
||||||
|
TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported"`
|
||||||
|
UserinfoEndpoint string `json:"userinfo_endpoint"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OAuth2Router struct {
|
||||||
|
options *OAuth2RouterOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewOAuth2Router creates a new OAuth2Router object
|
||||||
|
func NewOAuth2Router(options *OAuth2RouterOptions) *OAuth2Router {
|
||||||
|
options.Database.NewTable("oauth2")
|
||||||
|
|
||||||
|
//Read settings from database, if exists
|
||||||
|
options.Database.Read("oauth2", "oauth2WellKnownUrl", &options.OAuth2WellKnownUrl)
|
||||||
|
options.Database.Read("oauth2", "oauth2ServerUrl", &options.OAuth2ServerURL)
|
||||||
|
options.Database.Read("oauth2", "oauth2TokenUrl", &options.OAuth2TokenURL)
|
||||||
|
options.Database.Read("oauth2", "oauth2ClientId", &options.OAuth2ClientId)
|
||||||
|
options.Database.Read("oauth2", "oauth2ClientSecret", &options.OAuth2ClientSecret)
|
||||||
|
options.Database.Read("oauth2", "oauth2UserInfoUrl", &options.OAuth2UserInfoUrl)
|
||||||
|
options.Database.Read("oauth2", "oauth2Scopes", &options.OAuth2Scopes)
|
||||||
|
|
||||||
|
return &OAuth2Router{
|
||||||
|
options: options,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleSetOAuth2Settings is the internal handler for setting the OAuth URL and HTTPS
|
||||||
|
func (ar *OAuth2Router) HandleSetOAuth2Settings(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method == http.MethodGet {
|
||||||
|
//Return the current settings
|
||||||
|
js, _ := json.Marshal(map[string]interface{}{
|
||||||
|
"oauth2WellKnownUrl": ar.options.OAuth2WellKnownUrl,
|
||||||
|
"oauth2ServerUrl": ar.options.OAuth2ServerURL,
|
||||||
|
"oauth2TokenUrl": ar.options.OAuth2TokenURL,
|
||||||
|
"oauth2UserInfoUrl": ar.options.OAuth2UserInfoUrl,
|
||||||
|
"oauth2Scopes": ar.options.OAuth2Scopes,
|
||||||
|
"oauth2ClientSecret": ar.options.OAuth2ClientSecret,
|
||||||
|
"oauth2ClientId": ar.options.OAuth2ClientId,
|
||||||
|
})
|
||||||
|
|
||||||
|
utils.SendJSONResponse(w, string(js))
|
||||||
|
return
|
||||||
|
} else if r.Method == http.MethodPost {
|
||||||
|
//Update the settings
|
||||||
|
var oauth2ServerUrl, oauth2TokenURL, oauth2Scopes, oauth2UserInfoUrl string
|
||||||
|
oauth2WellKnownUrl, err := utils.PostPara(r, "oauth2WellKnownUrl")
|
||||||
|
if err != nil {
|
||||||
|
oauth2ServerUrl, err = utils.PostPara(r, "oauth2ServerUrl")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "oauth2ServerUrl not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
oauth2TokenURL, err = utils.PostPara(r, "oauth2TokenUrl")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "oauth2TokenUrl not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
oauth2Scopes, err = utils.PostPara(r, "oauth2Scopes")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "oauth2Scopes not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
oauth2UserInfoUrl, err = utils.PostPara(r, "oauth2UserInfoUrl")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "oauth2UserInfoUrl not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
oauth2ClientId, err := utils.PostPara(r, "oauth2ClientId")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "oauth2ClientId not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
oauth2ClientSecret, err := utils.PostPara(r, "oauth2ClientSecret")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "oauth2ClientSecret not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Write changes to runtime
|
||||||
|
ar.options.OAuth2WellKnownUrl = oauth2WellKnownUrl
|
||||||
|
ar.options.OAuth2ServerURL = oauth2ServerUrl
|
||||||
|
ar.options.OAuth2TokenURL = oauth2TokenURL
|
||||||
|
ar.options.OAuth2UserInfoUrl = oauth2UserInfoUrl
|
||||||
|
ar.options.OAuth2ClientId = oauth2ClientId
|
||||||
|
ar.options.OAuth2ClientSecret = oauth2ClientSecret
|
||||||
|
ar.options.OAuth2Scopes = oauth2Scopes
|
||||||
|
|
||||||
|
//Write changes to database
|
||||||
|
ar.options.Database.Write("oauth2", "oauth2WellKnownUrl", oauth2WellKnownUrl)
|
||||||
|
ar.options.Database.Write("oauth2", "oauth2ServerUrl", oauth2ServerUrl)
|
||||||
|
ar.options.Database.Write("oauth2", "oauth2TokenUrl", oauth2TokenURL)
|
||||||
|
ar.options.Database.Write("oauth2", "oauth2UserInfoUrl", oauth2UserInfoUrl)
|
||||||
|
ar.options.Database.Write("oauth2", "oauth2ClientId", oauth2ClientId)
|
||||||
|
ar.options.Database.Write("oauth2", "oauth2ClientSecret", oauth2ClientSecret)
|
||||||
|
ar.options.Database.Write("oauth2", "oauth2Scopes", oauth2Scopes)
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
} else {
|
||||||
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ar *OAuth2Router) fetchOAuth2Configuration(config *oauth2.Config) (*oauth2.Config, error) {
|
||||||
|
req, err := http.NewRequest("GET", ar.options.OAuth2WellKnownUrl, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
client := &http.Client{}
|
||||||
|
if resp, err := client.Do(req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
oidcDiscoveryDocument := OIDCDiscoveryDocument{}
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&oidcDiscoveryDocument); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(config.Scopes) == 0 {
|
||||||
|
config.Scopes = oidcDiscoveryDocument.ScopesSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Endpoint.AuthURL == "" {
|
||||||
|
config.Endpoint.AuthURL = oidcDiscoveryDocument.AuthorizationEndpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Endpoint.TokenURL == "" {
|
||||||
|
config.Endpoint.TokenURL = oidcDiscoveryDocument.TokenEndpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
if ar.options.OAuth2UserInfoUrl == "" {
|
||||||
|
ar.options.OAuth2UserInfoUrl = oidcDiscoveryDocument.UserinfoEndpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ar *OAuth2Router) newOAuth2Conf(redirectUrl string) (*oauth2.Config, error) {
|
||||||
|
config := &oauth2.Config{
|
||||||
|
ClientID: ar.options.OAuth2ClientId,
|
||||||
|
ClientSecret: ar.options.OAuth2ClientSecret,
|
||||||
|
RedirectURL: redirectUrl,
|
||||||
|
Endpoint: oauth2.Endpoint{
|
||||||
|
AuthURL: ar.options.OAuth2ServerURL,
|
||||||
|
TokenURL: ar.options.OAuth2TokenURL,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if ar.options.OAuth2Scopes != "" {
|
||||||
|
config.Scopes = strings.Split(ar.options.OAuth2Scopes, ",")
|
||||||
|
}
|
||||||
|
if ar.options.OAuth2WellKnownUrl != "" && (config.Endpoint.AuthURL == "" || config.Endpoint.TokenURL == "" ||
|
||||||
|
ar.options.OAuth2UserInfoUrl == "") {
|
||||||
|
return ar.fetchOAuth2Configuration(config)
|
||||||
|
}
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleOAuth2Auth is the internal handler for OAuth authentication
|
||||||
|
// Set useHTTPS to true if your OAuth server is using HTTPS
|
||||||
|
// Set OAuthURL to the URL of the OAuth server, e.g. OAuth.example.com
|
||||||
|
func (ar *OAuth2Router) HandleOAuth2Auth(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
const callbackPrefix = "/internal/oauth2"
|
||||||
|
const tokenCookie = "z-token"
|
||||||
|
scheme := "http"
|
||||||
|
if r.TLS != nil {
|
||||||
|
scheme = "https"
|
||||||
|
}
|
||||||
|
reqUrl := scheme + "://" + r.Host + r.RequestURI
|
||||||
|
oauthConfig, err := ar.newOAuth2Conf(scheme + "://" + r.Host + callbackPrefix)
|
||||||
|
if err != nil {
|
||||||
|
ar.options.Logger.PrintAndLog("OAuth2Router", "Failed to fetch OIDC configuration:", err)
|
||||||
|
w.WriteHeader(500)
|
||||||
|
return errors.New("failed to fetch OIDC configuration")
|
||||||
|
}
|
||||||
|
|
||||||
|
if oauthConfig.Endpoint.AuthURL == "" || oauthConfig.Endpoint.TokenURL == "" || ar.options.OAuth2UserInfoUrl == "" {
|
||||||
|
ar.options.Logger.PrintAndLog("OAuth2Router", "Invalid OAuth2 configuration", nil)
|
||||||
|
w.WriteHeader(500)
|
||||||
|
return errors.New("invalid OAuth2 configuration")
|
||||||
|
}
|
||||||
|
|
||||||
|
code := r.URL.Query().Get("code")
|
||||||
|
state := r.URL.Query().Get("state")
|
||||||
|
if r.Method == http.MethodGet && strings.HasPrefix(r.RequestURI, callbackPrefix) && code != "" && state != "" {
|
||||||
|
ctx := context.Background()
|
||||||
|
token, err := oauthConfig.Exchange(ctx, code)
|
||||||
|
if err != nil {
|
||||||
|
ar.options.Logger.PrintAndLog("OAuth2", "Token exchange failed", err)
|
||||||
|
w.WriteHeader(401)
|
||||||
|
return errors.New("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !token.Valid() {
|
||||||
|
ar.options.Logger.PrintAndLog("OAuth2", "Invalid token", err)
|
||||||
|
w.WriteHeader(401)
|
||||||
|
return errors.New("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
cookie := http.Cookie{Name: tokenCookie, Value: token.AccessToken, Path: "/"}
|
||||||
|
if scheme == "https" {
|
||||||
|
cookie.Secure = true
|
||||||
|
cookie.SameSite = http.SameSiteLaxMode
|
||||||
|
}
|
||||||
|
w.Header().Add("Set-Cookie", cookie.String())
|
||||||
|
|
||||||
|
//Fix for #695
|
||||||
|
location := strings.TrimPrefix(state, "/internal/")
|
||||||
|
//Check if the location starts with http:// or https://. if yes, this is full URL
|
||||||
|
decodedLocation, err := url.PathUnescape(location)
|
||||||
|
if err == nil && (strings.HasPrefix(decodedLocation, "http://") || strings.HasPrefix(decodedLocation, "https://")) {
|
||||||
|
//Redirect to the full URL
|
||||||
|
http.Redirect(w, r, decodedLocation, http.StatusTemporaryRedirect)
|
||||||
|
} else {
|
||||||
|
//Redirect to a relative path
|
||||||
|
http.Redirect(w, r, state, http.StatusTemporaryRedirect)
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.New("authorized")
|
||||||
|
}
|
||||||
|
unauthorized := false
|
||||||
|
cookie, err := r.Cookie(tokenCookie)
|
||||||
|
if err == nil {
|
||||||
|
if cookie.Value == "" {
|
||||||
|
unauthorized = true
|
||||||
|
} else {
|
||||||
|
ctx := context.Background()
|
||||||
|
client := oauthConfig.Client(ctx, &oauth2.Token{AccessToken: cookie.Value})
|
||||||
|
req, err := client.Get(ar.options.OAuth2UserInfoUrl)
|
||||||
|
if err != nil {
|
||||||
|
ar.options.Logger.PrintAndLog("OAuth2", "Failed to get user info", err)
|
||||||
|
unauthorized = true
|
||||||
|
}
|
||||||
|
defer req.Body.Close()
|
||||||
|
if req.StatusCode != http.StatusOK {
|
||||||
|
ar.options.Logger.PrintAndLog("OAuth2", "Failed to get user info", err)
|
||||||
|
unauthorized = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unauthorized = true
|
||||||
|
}
|
||||||
|
if unauthorized {
|
||||||
|
state := url.QueryEscape(reqUrl)
|
||||||
|
url := oauthConfig.AuthCodeURL(state, oauth2.AccessTypeOffline)
|
||||||
|
http.Redirect(w, r, url, http.StatusFound)
|
||||||
|
|
||||||
|
return errors.New("unauthorized")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@@ -46,6 +46,12 @@ func handleAuthProviderRouting(sep *ProxyEndpoint, w http.ResponseWriter, r *htt
|
|||||||
h.Parent.Option.Logger.LogHTTPRequest(r, "host-http", 401, requestHostname, "")
|
h.Parent.Option.Logger.LogHTTPRequest(r, "host-http", 401, requestHostname, "")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
case AuthMethodOauth2:
|
||||||
|
err := h.handleOAuth2Auth(w, r)
|
||||||
|
if err != nil {
|
||||||
|
h.Parent.Option.Logger.LogHTTPRequest(r, "host-http", 401, requestHostname, "")
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//No authentication provider, do not need to handle
|
//No authentication provider, do not need to handle
|
||||||
@@ -108,3 +114,7 @@ func handleBasicAuth(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint)
|
|||||||
func (h *ProxyHandler) handleForwardAuth(w http.ResponseWriter, r *http.Request) error {
|
func (h *ProxyHandler) handleForwardAuth(w http.ResponseWriter, r *http.Request) error {
|
||||||
return h.Parent.Option.ForwardAuthRouter.HandleAuthProviderRouting(w, r)
|
return h.Parent.Option.ForwardAuthRouter.HandleAuthProviderRouting(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *ProxyHandler) handleOAuth2Auth(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
return h.Parent.Option.OAuth2Router.HandleOAuth2Auth(w, r)
|
||||||
|
}
|
||||||
|
@@ -70,8 +70,9 @@ type ResponseRewriteRuleSet struct {
|
|||||||
DownstreamHeaders [][]string
|
DownstreamHeaders [][]string
|
||||||
|
|
||||||
/* Advance Usecase Options */
|
/* Advance Usecase Options */
|
||||||
HostHeaderOverwrite string //Force overwrite of request "Host" header (advanced usecase)
|
HostHeaderOverwrite string //Force overwrite of request "Host" header (advanced usecase)
|
||||||
NoRemoveHopByHop bool //Do not remove hop-by-hop headers (advanced usecase)
|
NoRemoveHopByHop bool //Do not remove hop-by-hop headers (advanced usecase)
|
||||||
|
DisableChunkedTransferEncoding bool //Disable chunked transfer encoding
|
||||||
|
|
||||||
/* System Information Payload */
|
/* System Information Payload */
|
||||||
Version string //Version number of Zoraxy, use for X-Proxy-By
|
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)
|
rewriteUserAgent(outreq.Header, "Zoraxy/"+rrr.Version)
|
||||||
|
|
||||||
//Fix proxmox transfer encoding bug if detected Proxmox Cookie
|
//Fix proxmox transfer encoding bug if detected Proxmox Cookie
|
||||||
if domainsniff.IsProxmox(req) {
|
if rrr.DisableChunkedTransferEncoding || domainsniff.IsProxmox(req) {
|
||||||
outreq.TransferEncoding = []string{"identity"}
|
outreq.TransferEncoding = []string{"identity"}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,7 +330,10 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
|
|||||||
locationRewrite := res.Header.Get("Location")
|
locationRewrite := res.Header.Get("Location")
|
||||||
originLocation := res.Header.Get("Location")
|
originLocation := res.Header.Get("Location")
|
||||||
res.Header.Set("zr-origin-location", originLocation)
|
res.Header.Set("zr-origin-location", originLocation)
|
||||||
|
decodedOriginLocation, err := url.PathUnescape(originLocation)
|
||||||
|
if err == nil {
|
||||||
|
originLocation = decodedOriginLocation
|
||||||
|
}
|
||||||
if strings.HasPrefix(originLocation, "http://") || strings.HasPrefix(originLocation, "https://") {
|
if strings.HasPrefix(originLocation, "http://") || strings.HasPrefix(originLocation, "https://") {
|
||||||
//Full path
|
//Full path
|
||||||
//Replace the forwarded target with expected Host
|
//Replace the forwarded target with expected Host
|
||||||
|
@@ -11,7 +11,6 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/domainsniff"
|
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/rewrite"
|
"imuslab.com/zoraxy/mod/dynamicproxy/rewrite"
|
||||||
"imuslab.com/zoraxy/mod/netutils"
|
"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
|
//Handle the request reverse proxy
|
||||||
statusCode, err := selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
statusCode, err := selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
||||||
ProxyDomain: selectedUpstream.OriginIpOrDomain,
|
ProxyDomain: selectedUpstream.OriginIpOrDomain,
|
||||||
OriginalHost: reqHostname,
|
OriginalHost: reqHostname,
|
||||||
UseTLS: selectedUpstream.RequireTLS,
|
UseTLS: selectedUpstream.RequireTLS,
|
||||||
NoCache: h.Parent.Option.NoCache,
|
NoCache: h.Parent.Option.NoCache,
|
||||||
PathPrefix: "",
|
PathPrefix: "",
|
||||||
UpstreamHeaders: upstreamHeaders,
|
UpstreamHeaders: upstreamHeaders,
|
||||||
DownstreamHeaders: downstreamHeaders,
|
DownstreamHeaders: downstreamHeaders,
|
||||||
HostHeaderOverwrite: headerRewriteOptions.RequestHostOverwrite,
|
DisableChunkedTransferEncoding: target.DisableChunkedTransferEncoding,
|
||||||
NoRemoveHopByHop: headerRewriteOptions.DisableHopByHopHeaderRemoval,
|
HostHeaderOverwrite: headerRewriteOptions.RequestHostOverwrite,
|
||||||
Version: target.parent.Option.HostVersion,
|
NoRemoveHopByHop: headerRewriteOptions.DisableHopByHopHeaderRemoval,
|
||||||
|
Version: target.parent.Option.HostVersion,
|
||||||
})
|
})
|
||||||
|
|
||||||
//validate the error
|
//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)
|
h.Parent.logRequest(r, true, 101, "vdir-websocket", r.Host, target.Domain)
|
||||||
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
|
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
|
||||||
SkipTLSValidation: target.SkipCertValidations,
|
SkipTLSValidation: target.SkipCertValidations,
|
||||||
SkipOriginCheck: target.parent.EnableWebsocketCustomHeaders, //You should not use websocket via virtual directory. But keep this to true for compatibility
|
SkipOriginCheck: true, //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
|
CopyAllHeaders: target.parent.EnableWebsocketCustomHeaders, //Left this as default to prevent nginx user setting / as vdir
|
||||||
UserDefinedHeaders: target.parent.HeaderRewriteRules.UserDefinedHeaders,
|
UserDefinedHeaders: target.parent.HeaderRewriteRules.UserDefinedHeaders,
|
||||||
Logger: h.Parent.Option.Logger,
|
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
|
//Handle the virtual directory reverse proxy request
|
||||||
statusCode, err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
statusCode, err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
||||||
ProxyDomain: target.Domain,
|
ProxyDomain: target.Domain,
|
||||||
OriginalHost: reqHostname,
|
OriginalHost: reqHostname,
|
||||||
UseTLS: target.RequireTLS,
|
UseTLS: target.RequireTLS,
|
||||||
PathPrefix: target.MatchingPath,
|
PathPrefix: target.MatchingPath,
|
||||||
UpstreamHeaders: upstreamHeaders,
|
UpstreamHeaders: upstreamHeaders,
|
||||||
DownstreamHeaders: downstreamHeaders,
|
DownstreamHeaders: downstreamHeaders,
|
||||||
HostHeaderOverwrite: headerRewriteOptions.RequestHostOverwrite,
|
DisableChunkedTransferEncoding: target.parent.DisableChunkedTransferEncoding,
|
||||||
Version: target.parent.parent.Option.HostVersion,
|
HostHeaderOverwrite: headerRewriteOptions.RequestHostOverwrite,
|
||||||
|
Version: target.parent.parent.Option.HostVersion,
|
||||||
})
|
})
|
||||||
|
|
||||||
var dnsError *net.DNSError
|
var dnsError *net.DNSError
|
||||||
|
@@ -13,6 +13,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/auth/sso/oauth2"
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/access"
|
"imuslab.com/zoraxy/mod/access"
|
||||||
"imuslab.com/zoraxy/mod/auth/sso/forward"
|
"imuslab.com/zoraxy/mod/auth/sso/forward"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||||
@@ -64,6 +66,7 @@ type RouterOption struct {
|
|||||||
|
|
||||||
/* Authentication Providers */
|
/* Authentication Providers */
|
||||||
ForwardAuthRouter *forward.AuthRouter
|
ForwardAuthRouter *forward.AuthRouter
|
||||||
|
OAuth2Router *oauth2.OAuth2Router //OAuth2Router router for OAuth2Router authentication
|
||||||
|
|
||||||
/* Utilities */
|
/* Utilities */
|
||||||
Logger *logger.Logger //Logger for reverse proxy requets
|
Logger *logger.Logger //Logger for reverse proxy requets
|
||||||
@@ -191,6 +194,9 @@ type ProxyEndpoint struct {
|
|||||||
//Uptime Monitor
|
//Uptime Monitor
|
||||||
DisableUptimeMonitor bool //Disable uptime monitor for this endpoint
|
DisableUptimeMonitor bool //Disable uptime monitor for this endpoint
|
||||||
|
|
||||||
|
// Chunked Transfer Encoding
|
||||||
|
DisableChunkedTransferEncoding bool //Disable chunked transfer encoding for this endpoint
|
||||||
|
|
||||||
//Access Control
|
//Access Control
|
||||||
AccessFilterUUID string //Access filter ID
|
AccessFilterUUID string //Access filter ID
|
||||||
|
|
||||||
|
@@ -1,14 +1,14 @@
|
|||||||
//go:build (windows && amd64) || (linux && mipsle) || (linux && riscv64) || (freebsd && amd64)
|
//go:build (windows && amd64) || (linux && mipsle) || (linux && riscv64) || (freebsd && amd64) || (darwin && arm64)
|
||||||
// +build windows,amd64 linux,mipsle linux,riscv64 freebsd,amd64
|
// +build windows,amd64 linux,mipsle linux,riscv64 freebsd,amd64 darwin,arm64
|
||||||
|
|
||||||
package sshprox
|
package sshprox
|
||||||
|
|
||||||
import "embed"
|
import "embed"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Binary embedding
|
Binary embedding
|
||||||
|
|
||||||
Make sure when compile, gotty binary exists in static.gotty
|
Make sure when compile, gotty binary exists in static.gotty
|
||||||
*/
|
*/
|
||||||
var (
|
var (
|
||||||
//go:embed gotty/LICENSE
|
//go:embed gotty/LICENSE
|
||||||
|
@@ -90,8 +90,8 @@ func (c *ProxyRelayConfig) ForwardUDP(address1, address2 string, stopChan chan b
|
|||||||
address1 = ":" + address1
|
address1 = ":" + address1
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(address1, ":") {
|
if strings.HasPrefix(address1, ":") {
|
||||||
//Prepend 127.0.0.1 to the address
|
//Prepend 0.0.0.0 to the address
|
||||||
address1 = "127.0.0.1" + address1
|
address1 = "0.0.0.0" + address1
|
||||||
}
|
}
|
||||||
|
|
||||||
lisener, targetAddr, err := initUDPConnections(address1, address2)
|
lisener, targetAddr, err := initUDPConnections(address1, address2)
|
||||||
|
@@ -17,22 +17,24 @@ import (
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
type StaticWebServerStatus struct {
|
type StaticWebServerStatus struct {
|
||||||
ListeningPort int
|
ListeningPort int
|
||||||
EnableDirectoryListing bool
|
EnableDirectoryListing bool
|
||||||
WebRoot string
|
WebRoot string
|
||||||
Running bool
|
Running bool
|
||||||
EnableWebDirManager bool
|
EnableWebDirManager bool
|
||||||
|
DisableListenToAllInterface bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle getting current static web server status
|
// Handle getting current static web server status
|
||||||
func (ws *WebServer) HandleGetStatus(w http.ResponseWriter, r *http.Request) {
|
func (ws *WebServer) HandleGetStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
listeningPortInt, _ := strconv.Atoi(ws.option.Port)
|
listeningPortInt, _ := strconv.Atoi(ws.option.Port)
|
||||||
currentStatus := StaticWebServerStatus{
|
currentStatus := StaticWebServerStatus{
|
||||||
ListeningPort: listeningPortInt,
|
ListeningPort: listeningPortInt,
|
||||||
EnableDirectoryListing: ws.option.EnableDirectoryListing,
|
EnableDirectoryListing: ws.option.EnableDirectoryListing,
|
||||||
WebRoot: ws.option.WebRoot,
|
WebRoot: ws.option.WebRoot,
|
||||||
Running: ws.isRunning,
|
Running: ws.isRunning,
|
||||||
EnableWebDirManager: ws.option.EnableWebDirManager,
|
EnableWebDirManager: ws.option.EnableWebDirManager,
|
||||||
|
DisableListenToAllInterface: ws.option.DisableListenToAllInterface,
|
||||||
}
|
}
|
||||||
|
|
||||||
js, _ := json.Marshal(currentStatus)
|
js, _ := json.Marshal(currentStatus)
|
||||||
@@ -91,3 +93,19 @@ func (ws *WebServer) SetEnableDirectoryListing(w http.ResponseWriter, r *http.Re
|
|||||||
ws.option.EnableDirectoryListing = enableList
|
ws.option.EnableDirectoryListing = enableList
|
||||||
utils.SendOK(w)
|
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/*
|
//go:embed templates/*
|
||||||
var templates embed.FS
|
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 {
|
type WebServerOptions struct {
|
||||||
Port string //Port for listening
|
Port string //Port for listening
|
||||||
EnableDirectoryListing bool //Enable listing of directory
|
EnableDirectoryListing bool //Enable listing of directory
|
||||||
WebRoot string //Folder for stroing the static web folders
|
WebRoot string //Folder for stroing the static web folders
|
||||||
EnableWebDirManager bool //Enable web file manager to handle files in web directory
|
EnableWebDirManager bool //Enable web file manager to handle files in web directory
|
||||||
Logger *logger.Logger //System logger
|
DisableListenToAllInterface bool // Disable listening to all interfaces, only listen to localhost
|
||||||
Sysdb *database.Database //Database for storing configs
|
Logger *logger.Logger //System logger
|
||||||
|
Sysdb *database.Database //Database for storing configs
|
||||||
}
|
}
|
||||||
|
|
||||||
type WebServer struct {
|
type WebServer struct {
|
||||||
@@ -92,6 +100,11 @@ func (ws *WebServer) RestorePreviousState() {
|
|||||||
ws.option.Sysdb.Read("webserv", "dirlist", &enableDirList)
|
ws.option.Sysdb.Read("webserv", "dirlist", &enableDirList)
|
||||||
ws.option.EnableDirectoryListing = 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
|
//Check the running state
|
||||||
webservRunning := true
|
webservRunning := true
|
||||||
ws.option.Sysdb.Read("webserv", "enabled", &webservRunning)
|
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")))
|
fs := http.FileServer(http.Dir(filepath.Join(ws.option.WebRoot, "html")))
|
||||||
ws.mux.Handle("/", ws.fsMiddleware(fs))
|
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{
|
ws.server = &http.Server{
|
||||||
Addr: ":" + ws.option.Port,
|
Addr: listenAddr,
|
||||||
Handler: ws.mux,
|
Handler: ws.mux,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -116,6 +116,7 @@ func ReverseProxtInit() {
|
|||||||
WebDirectory: *path_webserver,
|
WebDirectory: *path_webserver,
|
||||||
AccessController: accessController,
|
AccessController: accessController,
|
||||||
ForwardAuthRouter: forwardAuthRouter,
|
ForwardAuthRouter: forwardAuthRouter,
|
||||||
|
OAuth2Router: oauth2Router,
|
||||||
LoadBalancer: loadBalancer,
|
LoadBalancer: loadBalancer,
|
||||||
PluginManager: pluginManager,
|
PluginManager: pluginManager,
|
||||||
/* Utilities */
|
/* Utilities */
|
||||||
@@ -555,6 +556,9 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
proxyRateLimit = 1000
|
proxyRateLimit = 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Disable chunked Encoding
|
||||||
|
disableChunkedEncoding, _ := utils.PostBool(r, "dChunkedEnc")
|
||||||
|
|
||||||
//Load the previous basic auth credentials from current proxy rules
|
//Load the previous basic auth credentials from current proxy rules
|
||||||
targetProxyEntry, err := dynamicProxyRouter.LoadProxy(rootNameOrMatchingDomain)
|
targetProxyEntry, err := dynamicProxyRouter.LoadProxy(rootNameOrMatchingDomain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -595,6 +599,7 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
newProxyEndpoint.RateLimit = proxyRateLimit
|
newProxyEndpoint.RateLimit = proxyRateLimit
|
||||||
newProxyEndpoint.UseStickySession = useStickySession
|
newProxyEndpoint.UseStickySession = useStickySession
|
||||||
newProxyEndpoint.DisableUptimeMonitor = disbleUtm
|
newProxyEndpoint.DisableUptimeMonitor = disbleUtm
|
||||||
|
newProxyEndpoint.DisableChunkedTransferEncoding = disableChunkedEncoding
|
||||||
newProxyEndpoint.Tags = tags
|
newProxyEndpoint.Tags = tags
|
||||||
|
|
||||||
//Prepare to replace the current routing rule
|
//Prepare to replace the current routing rule
|
||||||
@@ -672,6 +677,83 @@ func ReverseProxyHandleAlias(w http.ResponseWriter, r *http.Request) {
|
|||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ReverseProxyHandleSetHostname(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
utils.SendErrorResponse(w, "Method not supported")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
originalRootnameOrMatchingDomain, err := utils.PostPara(r, "oldHostname")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "Invalid original hostname given")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newHostname, err := utils.PostPara(r, "newHostname")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "Invalid new hostname given")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
originalRootnameOrMatchingDomain = strings.TrimSpace(originalRootnameOrMatchingDomain)
|
||||||
|
newHostname = strings.TrimSpace(newHostname)
|
||||||
|
if newHostname == "/" {
|
||||||
|
//Reserevd, reutrn error
|
||||||
|
utils.SendErrorResponse(w, "Invalid new hostname: system reserved path")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check if the endpoint already exists
|
||||||
|
_, err = dynamicProxyRouter.LoadProxy(newHostname)
|
||||||
|
if err == nil {
|
||||||
|
//Endpoint already exists, return error
|
||||||
|
utils.SendErrorResponse(w, "Endpoint with this hostname already exists")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Clone, edit the endpoint and remove the original one
|
||||||
|
ept, err := dynamicProxyRouter.LoadProxy(originalRootnameOrMatchingDomain)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newEndpoint := ept.Clone()
|
||||||
|
newEndpoint.RootOrMatchingDomain = newHostname
|
||||||
|
|
||||||
|
//Prepare to replace the current routing rule
|
||||||
|
readyRoutingRule, err := dynamicProxyRouter.PrepareProxyRoute(newEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Remove the old endpoint from runtime
|
||||||
|
err = dynamicProxyRouter.RemoveProxyEndpointByRootname(originalRootnameOrMatchingDomain)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Remove the config from file
|
||||||
|
err = RemoveReverseProxyConfig(originalRootnameOrMatchingDomain)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add the new endpoint to runtime
|
||||||
|
dynamicProxyRouter.AddProxyRouteToRuntime(readyRoutingRule)
|
||||||
|
|
||||||
|
//Save it to file
|
||||||
|
SaveReverseProxyConfig(newEndpoint)
|
||||||
|
|
||||||
|
//Update uptime monitor targets
|
||||||
|
UpdateUptimeMonitorTargets()
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
||||||
|
|
||||||
func DeleteProxyEndpoint(w http.ResponseWriter, r *http.Request) {
|
func DeleteProxyEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||||
ep, err := utils.PostPara(r, "ep")
|
ep, err := utils.PostPara(r, "ep")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"imuslab.com/zoraxy/mod/auth/sso/oauth2"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -147,6 +148,11 @@ func startupSequence() {
|
|||||||
Database: sysdb,
|
Database: sysdb,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
oauth2Router = oauth2.NewOAuth2Router(&oauth2.OAuth2RouterOptions{
|
||||||
|
Logger: SystemWideLogger,
|
||||||
|
Database: sysdb,
|
||||||
|
})
|
||||||
|
|
||||||
//Create a statistic collector
|
//Create a statistic collector
|
||||||
statisticCollector, err = statistic.NewStatisticCollector(statistic.CollectorOption{
|
statisticCollector, err = statistic.NewStatisticCollector(statistic.CollectorOption{
|
||||||
Database: sysdb,
|
Database: sysdb,
|
||||||
|
File diff suppressed because it is too large
Load Diff
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
@@ -1,7 +1,7 @@
|
|||||||
<div class="standardContainer">
|
<div class="standardContainer">
|
||||||
<div class="ui basic segment">
|
<div class="ui basic segment">
|
||||||
<h2>Default Site</h2>
|
<h2>Default Site</h2>
|
||||||
<p>Default routing options for inbound traffic (previously called Proxy Root)</p>
|
<p>Default routing options for inbound traffic</p>
|
||||||
<div class="ui form">
|
<div class="ui form">
|
||||||
<div class="grouped fields">
|
<div class="grouped fields">
|
||||||
<label>What to show when Zoraxy is hit with an unknown Host?</label>
|
<label>What to show when Zoraxy is hit with an unknown Host?</label>
|
||||||
@@ -209,14 +209,13 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//Set the new proxy root option
|
//Set the new proxy root (aka default site) option
|
||||||
function setProxyRoot(btn=undefined){
|
function setProxyRoot(btn=undefined){
|
||||||
var newpr = $("#proxyRoot").val();
|
var newpr = $("#proxyRoot").val();
|
||||||
if (newpr.trim() == "" && currentDefaultSiteOption == 0){
|
if (newpr.trim() == "" && currentDefaultSiteOption == 0){
|
||||||
//Fill in the web server info
|
//Fill in the web server info
|
||||||
newpr = "127.0.0.1:" + $("#webserv_listenPort").val();
|
newpr = "127.0.0.1:" + $("#webserv_listenPort").val();
|
||||||
$("#proxyRoot").val(newpr);
|
$("#proxyRoot").val(newpr);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var rootReqTls = $("#rootReqTLS")[0].checked;
|
var rootReqTls = $("#rootReqTLS")[0].checked;
|
||||||
|
@@ -1,20 +1,17 @@
|
|||||||
<div class="standardContainer">
|
<div class="sso standardContainer">
|
||||||
<div class="ui basic segment">
|
<div class="ui basic segment">
|
||||||
<h2>SSO</h2>
|
<h2>SSO</h2>
|
||||||
<p>Single Sign-On (SSO) and authentication providers settings </p>
|
<p>Single Sign-On (SSO) and authentication providers settings </p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ui basic segment">
|
|
||||||
<div class="ui yellow message">
|
|
||||||
<div class="header">
|
|
||||||
Experimental Feature
|
|
||||||
</div>
|
|
||||||
<p>Please note that this feature is still in development and may not work as expected.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
<div class="ui basic segment">
|
<div class="ui top attached tabular menu ssoTabs">
|
||||||
<h3>Forward Auth</h3>
|
<a class="item active" data-tab="forward_auth_tab">Forward Auth</a>
|
||||||
|
<a class="item" data-tab="oauth2_tab">Oauth2</a>
|
||||||
|
<!-- <a class="item" data-tab="zoraxy_sso_tab">Zoraxy SSO</a> -->
|
||||||
|
</div>
|
||||||
|
<div class="ui bottom attached tab segment active" data-tab="forward_auth_tab">
|
||||||
|
<!-- Forward Auth -->
|
||||||
|
<h2>Forward Auth</h2>
|
||||||
<p>Configuration settings for the Forward Auth provider.</p>
|
<p>Configuration settings for the Forward Auth provider.</p>
|
||||||
<p>The Forward Auth provider makes a subrequest to an authorization server that supports Forward Auth, then either:</p>
|
<p>The Forward Auth provider makes a subrequest to an authorization server that supports Forward Auth, then either:</p>
|
||||||
<ul>
|
<ul>
|
||||||
@@ -25,8 +22,9 @@
|
|||||||
<ul>
|
<ul>
|
||||||
<li><a href="https://www.authelia.com" rel=”noopener noreferrer” target="_blank">Authelia</a></li>
|
<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://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>
|
</ul>
|
||||||
<form class="ui form">
|
<form class="ui form" action="#" id="forwardAuthSettings">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="forwardAuthAddress">Address</label>
|
<label for="forwardAuthAddress">Address</label>
|
||||||
<input type="text" id="forwardAuthAddress" name="forwardAuthAddress" placeholder="Enter Forward Auth Address">
|
<input type="text" id="forwardAuthAddress" name="forwardAuthAddress" placeholder="Enter Forward Auth Address">
|
||||||
@@ -42,38 +40,111 @@
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="forwardAuthResponseHeaders">Response Headers</label>
|
<label for="forwardAuthResponseHeaders">Response Headers</label>
|
||||||
<input type="text" id="forwardAuthResponseHeaders" name="forwardAuthResponseHeaders" placeholder="Enter Forward Auth Response Headers">
|
<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>
|
<small>
|
||||||
<strong>Example:</strong> <code>Remote-User,Remote-Groups,Remote-Email,Remote-Name</code></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>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="forwardAuthResponseClientHeaders">Response Client Headers</label>
|
<label for="forwardAuthResponseClientHeaders">Response Client Headers</label>
|
||||||
<input type="text" id="forwardAuthResponseClientHeaders" name="forwardAuthResponseClientHeaders" placeholder="Enter Forward Auth Response Client Headers">
|
<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>
|
<small>
|
||||||
<strong>Example:</strong> <code>Set-Cookie,WWW-Authenticate</code></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>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="forwardAuthRequestHeaders">Request Headers</label>
|
<label for="forwardAuthRequestHeaders">Request Headers</label>
|
||||||
<input type="text" id="forwardAuthRequestHeaders" name="forwardAuthRequestHeaders" placeholder="Enter Forward Auth Request Headers">
|
<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>
|
<small>
|
||||||
<strong>Example:</strong> <code>Cookie,Authorization</code></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>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="forwardAuthRequestExcludedCookies">Request Excluded Cookies</label>
|
<label for="forwardAuthRequestExcludedCookies">Request Excluded Cookies</label>
|
||||||
<input type="text" id="forwardAuthRequestExcludedCookies" name="forwardAuthRequestExcludedCookies" placeholder="Enter Forward Auth Request Excluded Cookies">
|
<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>
|
<small>
|
||||||
<strong>Example:</strong> <code>authelia_session,another_session</code></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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="ui basic button" onclick="event.preventDefault(); updateForwardAuthSettings();"><i class="green check icon"></i> Apply Change</button>
|
<button class="ui basic button" type="submit"><i class="green check icon"></i> Apply Change</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui divider"></div>
|
<div class="ui bottom attached tab segment" data-tab="oauth2_tab">
|
||||||
|
<!-- Oauth 2 -->
|
||||||
|
<h2>OAuth 2.0</h2>
|
||||||
|
<p>Configuration settings for OAuth 2.0 authentication provider.</p>
|
||||||
|
|
||||||
|
<form class="ui form" action="#" id="oauth2Settings">
|
||||||
|
<div class="field">
|
||||||
|
<label for="oauth2ClientId">Client ID</label>
|
||||||
|
<input type="text" id="oauth2ClientId" name="oauth2ClientId" placeholder="Enter Client ID">
|
||||||
|
<small>Public identifier of the OAuth2 application</small>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="oauth2ClientId">Client Secret</label>
|
||||||
|
<input type="password" id="oauth2ClientSecret" name="oauth2ClientSecret" placeholder="Enter Client Secret">
|
||||||
|
<small>Secret key of the OAuth2 application</small>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="oauth2WellKnownUrl">OIDC well-known URL</label>
|
||||||
|
<input type="text" id="oauth2WellKnownUrl" name="oauth2WellKnownUrl" placeholder="Enter Well-Known URL">
|
||||||
|
<small>URL to the OIDC discovery document (usually ending with /.well-known/openid-configuration). Used to automatically fetch provider settings.</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label for="oauth2ServerUrl">Authorization URL</label>
|
||||||
|
<input type="text" id="oauth2ServerUrl" name="oauth2ServerUrl" placeholder="Enter Authorization URL">
|
||||||
|
<small>URL used to authenticate against the OAuth2 provider. Will redirect the user to the OAuth2 provider login view. Optional if Well-Known url is configured.</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label for="oauth2TokenUrl">Token URL</label>
|
||||||
|
<input type="text" id="oauth2TokenUrl" name="oauth2TokenUrl" placeholder="Enter Token URL">
|
||||||
|
<small>URL used by Zoraxy to exchange a valid OAuth2 authentication code for an access token. Optional if Well-Known url is configured.</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label for="oauth2UserInfoURL">User Info URL</label>
|
||||||
|
<input type="text" id="oauth2UserInfoURL" name="oauth2UserInfoURL" placeholder="Enter User Info URL">
|
||||||
|
<small>URL used by the OAuth2 provider to validate generated token. Optional if Well-Known url is configured.</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label for="oauth2Scopes">Scopes</label>
|
||||||
|
<input type="text" id="oauth2Scopes" name="oauth2Scopes" placeholder="Enter Scopes">
|
||||||
|
<small>Scopes required by the OAuth2 provider to retrieve information about the authenticated user. Refer to your OAuth2 provider documentation for more information about this. Optional if Well-Known url is configured.</small>
|
||||||
|
</div>
|
||||||
|
<button class="ui basic button" type="submit"><i class="green check icon"></i> Apply Change</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="ui bottom attached tab segment" data-tab="zoraxy_sso_tab">
|
||||||
|
<!-- Zoraxy SSO -->
|
||||||
|
<h3>Zoraxy SSO</h3>
|
||||||
|
<p>Configuration settings for Zoraxy SSO provider.</p>
|
||||||
|
<p>Currently not implemented.</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
$(".ssoTabs .item").tab();
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
/* Load forward-auth settings from backend */
|
||||||
$.cjax({
|
$.cjax({
|
||||||
url: '/api/sso/forward-auth',
|
url: '/api/sso/forward-auth',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@@ -83,19 +154,46 @@
|
|||||||
$('#forwardAuthResponseHeaders').val(data.responseHeaders.join(","));
|
$('#forwardAuthResponseHeaders').val(data.responseHeaders.join(","));
|
||||||
$('#forwardAuthResponseClientHeaders').val(data.responseClientHeaders.join(","));
|
$('#forwardAuthResponseClientHeaders').val(data.responseClientHeaders.join(","));
|
||||||
$('#forwardAuthRequestHeaders').val(data.requestHeaders.join(","));
|
$('#forwardAuthRequestHeaders').val(data.requestHeaders.join(","));
|
||||||
|
$('#forwardAuthRequestIncludedCookies').val(data.requestIncludedCookies.join(","));
|
||||||
$('#forwardAuthRequestExcludedCookies').val(data.requestExcludedCookies.join(","));
|
$('#forwardAuthRequestExcludedCookies').val(data.requestExcludedCookies.join(","));
|
||||||
},
|
},
|
||||||
error: function(jqXHR, textStatus, errorThrown) {
|
error: function(jqXHR, textStatus, errorThrown) {
|
||||||
console.error('Error fetching SSO settings:', textStatus, errorThrown);
|
console.error('Error fetching SSO settings:', textStatus, errorThrown);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* Load Oauth2 settings from backend */
|
||||||
|
$.cjax({
|
||||||
|
url: '/api/sso/OAuth2',
|
||||||
|
method: 'GET',
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(data) {
|
||||||
|
$('#oauth2WellKnownUrl').val(data.oauth2WellKnownUrl);
|
||||||
|
$('#oauth2ServerUrl').val(data.oauth2ServerUrl);
|
||||||
|
$('#oauth2TokenUrl').val(data.oauth2TokenUrl);
|
||||||
|
$('#oauth2UserInfoUrl').val(data.oauth2UserInfoUrl);
|
||||||
|
$('#oauth2ClientId').val(data.oauth2ClientId);
|
||||||
|
$('#oauth2ClientSecret').val(data.oauth2ClientSecret);
|
||||||
|
$('#oauth2Scopes').val(data.oauth2Scopes);
|
||||||
|
},
|
||||||
|
error: function(jqXHR, textStatus, errorThrown) {
|
||||||
|
console.error('Error fetching SSO settings:', textStatus, errorThrown);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Add more initialization code here if needed */
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
Function to update Forward Auth settings.
|
||||||
|
*/
|
||||||
|
|
||||||
function updateForwardAuthSettings() {
|
function updateForwardAuthSettings() {
|
||||||
const address = $('#forwardAuthAddress').val();
|
const address = $('#forwardAuthAddress').val();
|
||||||
const responseHeaders = $('#forwardAuthResponseHeaders').val();
|
const responseHeaders = $('#forwardAuthResponseHeaders').val();
|
||||||
const responseClientHeaders = $('#forwardAuthResponseClientHeaders').val();
|
const responseClientHeaders = $('#forwardAuthResponseClientHeaders').val();
|
||||||
const requestHeaders = $('#forwardAuthRequestHeaders').val();
|
const requestHeaders = $('#forwardAuthRequestHeaders').val();
|
||||||
|
const requestIncludedCookies = $('#forwardAuthRequestIncludedCookies').val();
|
||||||
const requestExcludedCookies = $('#forwardAuthRequestExcludedCookies').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}.`);
|
console.log(`Updating Forward Auth settings. Address: ${address}. Response Headers: ${responseHeaders}. Response Client Headers: ${responseClientHeaders}. Request Headers: ${requestHeaders}. Request Excluded Cookies: ${requestExcludedCookies}.`);
|
||||||
@@ -123,4 +221,62 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$("#forwardAuthSettings").on("submit", function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
updateForwardAuthSettings();
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
Oauth2 settings update handler.
|
||||||
|
*/
|
||||||
|
$( "#authentikSettings" ).on( "submit", function( event ) {
|
||||||
|
event.preventDefault();
|
||||||
|
$.cjax({
|
||||||
|
url: '/api/sso/forward-auth',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
address: address,
|
||||||
|
responseHeaders: responseHeaders,
|
||||||
|
responseClientHeaders: responseClientHeaders,
|
||||||
|
requestHeaders: requestHeaders,
|
||||||
|
requestExcludedCookies: requestExcludedCookies
|
||||||
|
},
|
||||||
|
success: function(data) {
|
||||||
|
if (data.error !== undefined) {
|
||||||
|
msgbox(data.error, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
msgbox('Forward Auth settings updated', true);
|
||||||
|
console.log('Forward Auth settings updated:', data);
|
||||||
|
},
|
||||||
|
error: function(jqXHR, textStatus, errorThrown) {
|
||||||
|
console.error('Error updating Forward Auth settings:', textStatus, errorThrown);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$( "#oauth2Settings" ).on( "submit", function( event ) {
|
||||||
|
event.preventDefault();
|
||||||
|
$.cjax({
|
||||||
|
url: '/api/sso/OAuth2',
|
||||||
|
method: 'POST',
|
||||||
|
data: $( this ).serialize(),
|
||||||
|
success: function(data) {
|
||||||
|
if (data.error != undefined) {
|
||||||
|
msgbox(data.error, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
msgbox('OAuth2 settings updated', true);
|
||||||
|
console.log('OAuth2 settings updated:', data);
|
||||||
|
},
|
||||||
|
error: function(jqXHR, textStatus, errorThrown) {
|
||||||
|
console.error('Error updating OAuth2 settings:', textStatus, errorThrown);
|
||||||
|
msgbox('Error updating OAuth2 settings, check console', false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Bind UI events */
|
||||||
|
$(".sso .advanceSettings").accordion();
|
||||||
</script>
|
</script>
|
@@ -29,6 +29,13 @@
|
|||||||
<small>If this folder do not contains any index files, list the directory of this folder.</small>
|
<small>If this folder do not contains any index files, list the directory of this folder.</small>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="field">
|
||||||
<label>Document Root Folder</label>
|
<label>Document Root Folder</label>
|
||||||
<input id="webserv_docRoot" type="text" readonly="true">
|
<input id="webserv_docRoot" type="text" readonly="true">
|
||||||
@@ -136,6 +143,13 @@
|
|||||||
$("#webserv_dirManager").remove();
|
$("#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);
|
$("#webserv_listenPort").val(data.ListeningPort);
|
||||||
updateWebServLinkExample(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(){
|
$("#webserv_listenPort").off("change").on("change", function(){
|
||||||
let newPort = $(this).val();
|
let newPort = $(this).val();
|
||||||
|
|
||||||
|
@@ -184,7 +184,8 @@ body.darkTheme .ui.input input::placeholder {
|
|||||||
body.darkTheme .ui.label,
|
body.darkTheme .ui.label,
|
||||||
body.darkTheme .ui.label .detail,
|
body.darkTheme .ui.label .detail,
|
||||||
body.darkTheme .ui.label .icon {
|
body.darkTheme .ui.label .icon {
|
||||||
color: #ffffff !important;
|
background-color: var(--buttom_toggle_disabled);
|
||||||
|
color: var(--text_color) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.darkTheme .advanceoptions .title {
|
body.darkTheme .advanceoptions .title {
|
||||||
|
@@ -72,7 +72,7 @@
|
|||||||
<i class="simplistic lock icon"></i> TLS / SSL certificates
|
<i class="simplistic lock icon"></i> TLS / SSL certificates
|
||||||
</a>
|
</a>
|
||||||
<a class="item" tag="sso">
|
<a class="item" tag="sso">
|
||||||
<i class="simplistic user circle icon"></i> SSO / Oauth
|
<i class="simplistic user circle icon"></i> SSO / OAuth2
|
||||||
</a>
|
</a>
|
||||||
<div class="ui divider menudivider">Others</div>
|
<div class="ui divider menudivider">Others</div>
|
||||||
<a class="item" tag="webserv">
|
<a class="item" tag="webserv">
|
||||||
@@ -120,7 +120,7 @@
|
|||||||
<!-- Create Rules -->
|
<!-- Create Rules -->
|
||||||
<div id="rules" class="functiontab" target="rules.html"></div>
|
<div id="rules" class="functiontab" target="rules.html"></div>
|
||||||
|
|
||||||
<!-- Set proxy root -->
|
<!-- Set default site -->
|
||||||
<div id="setroot" class="functiontab" target="rproot.html"></div>
|
<div id="setroot" class="functiontab" target="rproot.html"></div>
|
||||||
|
|
||||||
<!-- Set TLS cert -->
|
<!-- Set TLS cert -->
|
||||||
@@ -334,6 +334,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function toggleTheme(){
|
function toggleTheme(){
|
||||||
|
let editorSideWrapper = $("#httprpEditModal .wrapper_frame");
|
||||||
if ($("body").hasClass("darkTheme")){
|
if ($("body").hasClass("darkTheme")){
|
||||||
setDarkTheme(false);
|
setDarkTheme(false);
|
||||||
//Check if the snippet iframe is opened. If yes, set the dark theme to the iframe
|
//Check if the snippet iframe is opened. If yes, set the dark theme to the iframe
|
||||||
@@ -341,6 +342,12 @@
|
|||||||
$(".sideWrapper iframe")[0].contentWindow.setDarkTheme(false);
|
$(".sideWrapper iframe")[0].contentWindow.setDarkTheme(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$(editorSideWrapper).each(function(){
|
||||||
|
if ($(this)[0].contentWindow.setDarkTheme){
|
||||||
|
$(this)[0].contentWindow.setDarkTheme(false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
if ($("#pluginContextLoader").is(":visible")){
|
if ($("#pluginContextLoader").is(":visible")){
|
||||||
$("#pluginContextLoader")[0].contentWindow.setDarkTheme(false);
|
$("#pluginContextLoader")[0].contentWindow.setDarkTheme(false);
|
||||||
}
|
}
|
||||||
@@ -350,6 +357,11 @@
|
|||||||
if ($(".sideWrapper").is(":visible")){
|
if ($(".sideWrapper").is(":visible")){
|
||||||
$(".sideWrapper iframe")[0].contentWindow.setDarkTheme(true);
|
$(".sideWrapper iframe")[0].contentWindow.setDarkTheme(true);
|
||||||
}
|
}
|
||||||
|
$(editorSideWrapper).each(function(){
|
||||||
|
if ($(this)[0].contentWindow.setDarkTheme){
|
||||||
|
$(this)[0].contentWindow.setDarkTheme(true);
|
||||||
|
}
|
||||||
|
})
|
||||||
if ($("#pluginContextLoader").is(":visible")){
|
if ($("#pluginContextLoader").is(":visible")){
|
||||||
$("#pluginContextLoader")[0].contentWindow.setDarkTheme(true);
|
$("#pluginContextLoader")[0].contentWindow.setDarkTheme(true);
|
||||||
}
|
}
|
||||||
@@ -515,6 +527,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function hideSideWrapper(discardFrameContent = false){
|
function hideSideWrapper(discardFrameContent = false){
|
||||||
|
if ($("#httprpEditModal").length && $("#httprpEditModal").is(":visible")) {
|
||||||
|
//HTTP Proxy Rule editor side wrapper implementation
|
||||||
|
$("#httprpEditModal .editor_side_wrapper").hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Original side wrapper implementation
|
||||||
if (discardFrameContent){
|
if (discardFrameContent){
|
||||||
$(".sideWrapper iframe").attr("src", "snippet/placeholder.html");
|
$(".sideWrapper iframe").attr("src", "snippet/placeholder.html");
|
||||||
}
|
}
|
||||||
|
@@ -264,7 +264,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
document.getElementById('accessRuleSelector').addEventListener('change', handleSelectEditingAccessRule);
|
document.getElementById('accessRuleSelector').addEventListener('change', handleSelectEditingAccessRule);
|
||||||
document.getElementById('accessRuleForm').addEventListener('submit', handleCreateNewAccessRule);
|
document.getElementById('accessRuleForm').addEventListener('submit', handleCreateNewAccessRule);
|
||||||
|
|
||||||
|
@@ -14,17 +14,18 @@
|
|||||||
<script src="../script/darktheme.js"></script>
|
<script src="../script/darktheme.js"></script>
|
||||||
<br>
|
<br>
|
||||||
<div class="ui container">
|
<div class="ui container">
|
||||||
|
<!--
|
||||||
<div class="ui header">
|
<div class="ui header">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
Alias Hostname
|
Alias Hostname
|
||||||
<div class="sub header epname"></div>
|
<div class="sub header epname"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>-->
|
||||||
<div class="scrolling content ui form">
|
<div class="scrolling content ui form">
|
||||||
<div id="inlineEditBasicAuthCredentials" class="field">
|
<div id="inlineEditBasicAuthCredentials" class="field">
|
||||||
<p>Enter alias hostname or wildcard matching keywords for <code class="epname"></code></p>
|
<p>Enter alias hostname or wildcard matching keywords for <code class="epname"></code></p>
|
||||||
<table class="ui very basic compacted unstackable celled table">
|
<table class="ui basic very compact unstackable celled table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Alias Hostname</th>
|
<th>Alias Hostname</th>
|
||||||
@@ -50,10 +51,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui divider"></div>
|
|
||||||
<div class="field" >
|
|
||||||
<button class="ui basic button" style="float: right;" onclick="closeThisWrapper();">Close</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br><br><br><br>
|
<br><br><br><br>
|
||||||
@@ -164,7 +161,7 @@
|
|||||||
}
|
}
|
||||||
$("#inlineEditTable").append(`<tr>
|
$("#inlineEditTable").append(`<tr>
|
||||||
<td>${domainLink}</td>
|
<td>${domainLink}</td>
|
||||||
<td><button class="ui basic button" onclick="removeAliasDomain('${aliasDomain}');"><i class="red remove icon"></i> Remove</button></td>
|
<td><button class="ui basic mini circular icon button" onclick="removeAliasDomain('${aliasDomain}');"><i class="red trash icon"></i></button></td>
|
||||||
</tr>`);
|
</tr>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -14,18 +14,11 @@
|
|||||||
<script src="../script/darktheme.js"></script>
|
<script src="../script/darktheme.js"></script>
|
||||||
<br>
|
<br>
|
||||||
<div class="ui container">
|
<div class="ui container">
|
||||||
<div class="ui header">
|
|
||||||
<div class="content">
|
|
||||||
Basic Auth Settings
|
|
||||||
<div class="sub header" id="epname"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="ui divider"></div>
|
|
||||||
<h3 class="ui header">Basic Auth Credential</h3>
|
<h3 class="ui header">Basic Auth Credential</h3>
|
||||||
<div class="scrolling content ui form">
|
<div class="scrolling content ui form">
|
||||||
<div id="inlineEditBasicAuthCredentials" class="field">
|
<div id="inlineEditBasicAuthCredentials" class="field">
|
||||||
<p>Enter the username and password for allowing them to access this proxy endpoint</p>
|
<p>Enter the username and password for allowing them to access this proxy endpoint</p>
|
||||||
<table class="ui very basic compacted unstackable celled table">
|
<table class="ui basic very compacted unstackable celled table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Username</th>
|
<th>Username</th>
|
||||||
@@ -56,7 +49,7 @@
|
|||||||
<h3 class="ui header">Authentication Exclusion Paths</h3>
|
<h3 class="ui header">Authentication Exclusion Paths</h3>
|
||||||
<div class="scrolling content ui form">
|
<div class="scrolling content ui form">
|
||||||
<p>Exclude specific directories / paths which contains the following subpath prefix from authentication. Useful if you are hosting services require remote API access.</p>
|
<p>Exclude specific directories / paths which contains the following subpath prefix from authentication. Useful if you are hosting services require remote API access.</p>
|
||||||
<table class="ui very basic compacted unstackable celled table">
|
<table class="ui basic very compacted unstackable celled table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Path Prefix</th>
|
<th>Path Prefix</th>
|
||||||
@@ -86,10 +79,6 @@
|
|||||||
<code>/public/res/far/boo/</code></p>
|
<code>/public/res/far/boo/</code></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui divider"></div>
|
|
||||||
<div class="field" >
|
|
||||||
<button class="ui basic button" style="float: right;" onclick="closeThisWrapper();">Close</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br><br><br><br>
|
<br><br><br><br>
|
||||||
@@ -232,7 +221,7 @@
|
|||||||
data.forEach(function(rule){
|
data.forEach(function(rule){
|
||||||
$("#exclusionPaths").append(` <tr>
|
$("#exclusionPaths").append(` <tr>
|
||||||
<td>${rule.PathPrefix}</td>
|
<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>
|
<td><button class="ui red basic mini circular icon button" onclick="removeExceptionPath(this);" prefix="${rule.PathPrefix}"><i class="ui red times icon"></i></button></td>
|
||||||
</tr>`);
|
</tr>`);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -261,7 +250,7 @@
|
|||||||
var row = '<tr>' +
|
var row = '<tr>' +
|
||||||
'<td>' + username + '</td>' +
|
'<td>' + username + '</td>' +
|
||||||
'<td>' + password + '</td>' +
|
'<td>' + password + '</td>' +
|
||||||
'<td><button class="ui basic button" onclick="removeCredentialFromEditingList(' + i + ');"><i class="red remove icon"></i> Remove</button></td>' +
|
'<td><button class="ui basic tiny circular button" onclick="removeCredentialFromEditingList(' + i + ');"><i class="red remove icon"></i> Remove</button></td>' +
|
||||||
'</tr>';
|
'</tr>';
|
||||||
|
|
||||||
tableBody.append(row);
|
tableBody.append(row);
|
||||||
|
@@ -27,6 +27,11 @@
|
|||||||
body.darkTheme #permissionPolicyEditor .experimental{
|
body.darkTheme #permissionPolicyEditor .experimental{
|
||||||
background-color: rgb(41, 41, 41);
|
background-color: rgb(41, 41, 41);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.advanceoptions{
|
||||||
|
background: var(--theme_advance) !important;
|
||||||
|
border-radius: 0.4em !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -34,19 +39,12 @@
|
|||||||
<script src="../script/darktheme.js"></script>
|
<script src="../script/darktheme.js"></script>
|
||||||
<br>
|
<br>
|
||||||
<div class="ui container">
|
<div class="ui container">
|
||||||
<div class="ui header">
|
|
||||||
<div class="content">
|
|
||||||
Custom Headers
|
|
||||||
<div class="sub header" id="epname"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="ui divider"></div>
|
|
||||||
<div class="ui small pointing secondary menu">
|
<div class="ui small pointing secondary menu">
|
||||||
<a class="item active narrowpadding" data-tab="customheaders">Custom Headers</a>
|
<a class="item active narrowpadding" data-tab="customheaders">Custom Headers</a>
|
||||||
<a class="item narrowpadding" data-tab="security">Security Headers</a>
|
<a class="item narrowpadding" data-tab="security">Security Headers</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui tab basic segment active" data-tab="customheaders">
|
<div class="ui tab basic segment active" data-tab="customheaders">
|
||||||
<table class="ui very basic compacted unstackable celled table">
|
<table class="ui basic very compacted unstackable celled table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Key</th>
|
<th>Key</th>
|
||||||
@@ -171,10 +169,6 @@
|
|||||||
<br><br>
|
<br><br>
|
||||||
<button class="ui basic button" onclick="savePermissionPolicy();"><i class="green save icon"></i> Save</button>
|
<button class="ui basic button" onclick="savePermissionPolicy();"><i class="green save icon"></i> Save</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field" >
|
|
||||||
<button class="ui basic button" style="float: right;" onclick="closeThisWrapper();">Close</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br><br><br><br>
|
<br><br><br><br>
|
||||||
@@ -189,7 +183,7 @@
|
|||||||
let payloadHash = window.location.hash.substr(1);
|
let payloadHash = window.location.hash.substr(1);
|
||||||
try{
|
try{
|
||||||
payloadHash = JSON.parse(decodeURIComponent(payloadHash));
|
payloadHash = JSON.parse(decodeURIComponent(payloadHash));
|
||||||
$("#epname").text(payloadHash.ep);
|
//$("#epname").text(payloadHash.ep);
|
||||||
editingEndpoint = payloadHash;
|
editingEndpoint = payloadHash;
|
||||||
}catch(ex){
|
}catch(ex){
|
||||||
console.log("Unable to load endpoint data from hash")
|
console.log("Unable to load endpoint data from hash")
|
||||||
|
@@ -35,7 +35,7 @@
|
|||||||
|
|
||||||
#accessRuleList{
|
#accessRuleList{
|
||||||
padding: 0.6em;
|
padding: 0.6em;
|
||||||
border: 1px solid rgb(228, 228, 228);
|
/* border: 1px solid rgb(228, 228, 228); */
|
||||||
border-radius: 0.4em !important;
|
border-radius: 0.4em !important;
|
||||||
max-height: calc(100vh - 15em);
|
max-height: calc(100vh - 15em);
|
||||||
min-height: 300px;
|
min-height: 300px;
|
||||||
@@ -65,13 +65,6 @@
|
|||||||
<script src="../script/darktheme.js"></script>
|
<script src="../script/darktheme.js"></script>
|
||||||
<br>
|
<br>
|
||||||
<div class="ui container">
|
<div class="ui container">
|
||||||
<div class="ui header">
|
|
||||||
<div class="content">
|
|
||||||
Host Access Settings
|
|
||||||
<div class="sub header" id="epname"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="ui divider"></div>
|
|
||||||
<p>Select an access rule to apply blacklist / whitelist filtering</p>
|
<p>Select an access rule to apply blacklist / whitelist filtering</p>
|
||||||
<div id="accessRuleList">
|
<div id="accessRuleList">
|
||||||
<div class="ui segment accessRule">
|
<div class="ui segment accessRule">
|
||||||
@@ -85,9 +78,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br>
|
<br>
|
||||||
<button class="ui basic button" onclick="applyChangeAndClose()"><i class="ui green check icon"></i> Apply Change</button>
|
<!-- <button class="ui basic button" onclick="applyChange()"><i class="ui green check icon"></i> Apply Change</button> -->
|
||||||
|
|
||||||
<button class="ui basic button" style="float: right;" onclick="parent.hideSideWrapper();"><i class="remove icon"></i> Close</button>
|
|
||||||
<br><br><br>
|
<br><br><br>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -174,6 +165,35 @@
|
|||||||
let accessRuleID = $(accessRuleObject).attr("ruleid");
|
let accessRuleID = $(accessRuleObject).attr("ruleid");
|
||||||
$(".accessRule").removeClass('active');
|
$(".accessRule").removeClass('active');
|
||||||
$(accessRuleObject).addClass('active');
|
$(accessRuleObject).addClass('active');
|
||||||
|
|
||||||
|
//Updates 2025-06-10: Added auto save on change feature
|
||||||
|
applyChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function applyChange(){
|
||||||
|
let newAccessRuleID = $(".accessRule.active").attr("ruleid");
|
||||||
|
let targetEndpoint = editingEndpoint.ep;
|
||||||
|
$.cjax({
|
||||||
|
url: "/api/access/attach",
|
||||||
|
method: "POST",
|
||||||
|
data: {
|
||||||
|
id: newAccessRuleID,
|
||||||
|
host: targetEndpoint
|
||||||
|
},
|
||||||
|
success: function(data){
|
||||||
|
if (data.error != undefined){
|
||||||
|
parent.msgbox(data.error, false);
|
||||||
|
}else{
|
||||||
|
parent.msgbox("Access Rule Updated");
|
||||||
|
|
||||||
|
//Modify the parent list if exists
|
||||||
|
if (parent != undefined && parent.updateAccessRuleNameUnderHost){
|
||||||
|
parent.updateAccessRuleNameUnderHost(targetEndpoint, newAccessRuleID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyChangeAndClose(){
|
function applyChangeAndClose(){
|
||||||
|
@@ -21,15 +21,8 @@
|
|||||||
<script src="../script/darktheme.js"></script>
|
<script src="../script/darktheme.js"></script>
|
||||||
<br>
|
<br>
|
||||||
<div class="ui container">
|
<div class="ui container">
|
||||||
<div class="ui header">
|
|
||||||
<div class="content">
|
|
||||||
Edit Tags
|
|
||||||
<div class="sub header" id="epname"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="ui divider"></div>
|
|
||||||
<p>Tags currently applied to this host name / proxy rule</p>
|
<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">
|
<table class="ui compact basic unstackable celled table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -68,9 +61,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<button class="ui basic button" onclick="joinSelectedTagGroups();"><i class="ui blue plus icon"></i> Join tag group(s)</button>
|
<button class="ui basic button" onclick="joinSelectedTagGroups();"><i class="ui blue plus icon"></i> Join tag group(s)</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui divider"></div>
|
<br><br>
|
||||||
<!-- <button class="ui basic button" onclick="saveTags();"><i class="ui green save icon"></i> Save Changes</button> -->
|
|
||||||
<button class="ui basic button" style="float: right;" onclick="parent.hideSideWrapper();"><i class="remove icon"></i> Close</button>
|
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
let editingEndpoint = {};
|
let editingEndpoint = {};
|
||||||
@@ -164,6 +155,10 @@
|
|||||||
|
|
||||||
function addSelectedTags() {
|
function addSelectedTags() {
|
||||||
let tags = $('#tagsInput').val().split(',').map(tag => tag.trim());
|
let tags = $('#tagsInput').val().split(',').map(tag => tag.trim());
|
||||||
|
if (tags.length == 0 || (tags.length == 1 && tags[0] == "")){
|
||||||
|
parent.msgbox("Please enter at least one tag", false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
tags.forEach(tag => {
|
tags.forEach(tag => {
|
||||||
if (tag && !tagAlreadyExistsInTable(tag)) {
|
if (tag && !tagAlreadyExistsInTable(tag)) {
|
||||||
addTagRow(tag);
|
addTagRow(tag);
|
||||||
@@ -210,8 +205,8 @@
|
|||||||
const row = `<tr class="tagEntry" value="${tag}">
|
const row = `<tr class="tagEntry" value="${tag}">
|
||||||
<td><div class="ui circular label tag-color" style="background-color: ${getTagColorByName(tag)};"></div> ${tag}</td>
|
<td><div class="ui circular label tag-color" style="background-color: ${getTagColorByName(tag)};"></div> ${tag}</td>
|
||||||
<td>
|
<td>
|
||||||
<button title="Delete Tag" class="ui circular mini red basic icon button" onclick="removeTag('${tag}')">
|
<button title="Delete Tag" class="ui circular mini basic button" onclick="removeTag('${tag}')">
|
||||||
<i class="trash icon"></i>
|
<i class="red trash icon"></i> Delete
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
|
@@ -75,13 +75,6 @@
|
|||||||
<script src="../script/darktheme.js"></script>
|
<script src="../script/darktheme.js"></script>
|
||||||
<br>
|
<br>
|
||||||
<div class="ui container">
|
<div class="ui container">
|
||||||
<div class="ui header">
|
|
||||||
<div class="content">
|
|
||||||
Upstreams / Load Balance
|
|
||||||
<div class="sub header epname"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="ui divider"></div>
|
|
||||||
<div class="ui small pointing secondary menu">
|
<div class="ui small pointing secondary menu">
|
||||||
<a class="item active narrowpadding" data-tab="upstreamlist">Upstreams</a>
|
<a class="item active narrowpadding" data-tab="upstreamlist">Upstreams</a>
|
||||||
<a class="item narrowpadding" data-tab="newupstream">Add Upstream</a>
|
<a class="item narrowpadding" data-tab="newupstream">Add Upstream</a>
|
||||||
@@ -159,10 +152,6 @@
|
|||||||
<br><br>
|
<br><br>
|
||||||
<button class="ui basic button" onclick="addNewUpstream();"><i class="ui green circle add icon"></i> Create</button>
|
<button class="ui basic button" onclick="addNewUpstream();"><i class="ui green circle add icon"></i> Create</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui divider"></div>
|
|
||||||
<div class="field" >
|
|
||||||
<button class="ui basic button" style="float: right;" onclick="closeThisWrapper();">Close</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<br><br><br><br>
|
<br><br><br><br>
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user