From 61b873451f140ddce2e539315bad287e06ff73a1 Mon Sep 17 00:00:00 2001 From: Krzysztof Jagosz Date: Tue, 29 Apr 2025 01:05:48 +0200 Subject: [PATCH 1/7] Added OAuth2 support for SSO --- .gitignore | 6 + src/api.go | 1 + src/def.go | 2 + src/go.mod | 2 +- src/mod/auth/sso/authentik/authentik.go | 2 +- src/mod/auth/sso/oauth2/oauth2.go | 297 ++++++++++++++++++++++++ src/mod/dynamicproxy/authProviders.go | 10 + src/mod/dynamicproxy/typedef.go | 4 +- src/mod/sshprox/embed.go | 8 +- src/reverseproxy.go | 3 +- src/start.go | 6 + src/web/components/httprp.html | 8 +- src/web/components/sso.html | 140 ++++++++--- src/web/index.html | 2 +- 14 files changed, 454 insertions(+), 37 deletions(-) create mode 100644 src/mod/auth/sso/oauth2/oauth2.go diff --git a/.gitignore b/.gitignore index 5c9767d..fabfcb3 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,9 @@ src/log/ example/plugins/ztnc/ztnc.db example/plugins/ztnc/authtoken.secret example/plugins/ztnc/ztnc.db.lock +.idea +conf +log +tmp +sys.* +www/html/index.html diff --git a/src/api.go b/src/api.go index 01adf20..a997737 100644 --- a/src/api.go +++ b/src/api.go @@ -84,6 +84,7 @@ func RegisterTLSAPIs(authRouter *auth.RouterDef) { func RegisterAuthenticationHandlerAPIs(authRouter *auth.RouterDef) { authRouter.HandleFunc("/api/sso/Authelia", autheliaRouter.HandleSetAutheliaURLAndHTTPS) authRouter.HandleFunc("/api/sso/Authentik", authentikRouter.HandleSetAuthentikURLAndHTTPS) + authRouter.HandleFunc("/api/sso/OAuth2", oauth2Router.HandleSetOAuth2Settings) } // Register the APIs for redirection rules management functions diff --git a/src/def.go b/src/def.go index 7efd638..60aacdd 100644 --- a/src/def.go +++ b/src/def.go @@ -10,6 +10,7 @@ package main import ( "embed" "flag" + "imuslab.com/zoraxy/mod/auth/sso/oauth2" "net/http" "time" @@ -146,6 +147,7 @@ var ( //Authentication Provider autheliaRouter *authelia.AutheliaRouter //Authelia router for Authelia authentication authentikRouter *authentik.AuthentikRouter //Authentik router for Authentik authentication + oauth2Router *oauth2.OAuth2Router //OAuth2Router router for OAuth2Router authentication //Helper modules EmailSender *email.Sender //Email sender that handle email sending diff --git a/src/go.mod b/src/go.mod index e722aa9..3b0d5e4 100644 --- a/src/go.mod +++ b/src/go.mod @@ -16,6 +16,7 @@ require ( github.com/grandcat/zeroconf v1.0.0 github.com/likexian/whois v1.15.1 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/syndtr/goleveldb v1.0.0 golang.org/x/net v0.33.0 @@ -32,7 +33,6 @@ require ( 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 diff --git a/src/mod/auth/sso/authentik/authentik.go b/src/mod/auth/sso/authentik/authentik.go index 795b0b3..a4abbc6 100644 --- a/src/mod/auth/sso/authentik/authentik.go +++ b/src/mod/auth/sso/authentik/authentik.go @@ -56,7 +56,7 @@ func (ar *AuthentikRouter) HandleSetAuthentikURLAndHTTPS(w http.ResponseWriter, return } - useHTTPS, err := utils.PostBool(r, "useHTTPS") + useHTTPS, err := utils.PostBool(r, "authentikUseHttps") if err != nil { useHTTPS = false } diff --git a/src/mod/auth/sso/oauth2/oauth2.go b/src/mod/auth/sso/oauth2/oauth2.go new file mode 100644 index 0000000..e036d6f --- /dev/null +++ b/src/mod/auth/sso/oauth2/oauth2.go @@ -0,0 +1,297 @@ +package oauth2 + +import ( + "context" + "encoding/json" + "errors" + "golang.org/x/oauth2" + "imuslab.com/zoraxy/mod/database" + "imuslab.com/zoraxy/mod/info/logger" + "imuslab.com/zoraxy/mod/utils" + "net/http" + "net/url" + "strings" +) + +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 + OAuth2RedirectUrl string //The redirect 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", "oauth2RedirectURL", &options.OAuth2RedirectUrl) + 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, + "oauth2Scopes": ar.options.OAuth2Scopes, + "oauth2ClientSecret": ar.options.OAuth2ClientSecret, + "oauth2RedirectURL": ar.options.OAuth2RedirectUrl, + "oauth2UserInfoURL": ar.options.OAuth2UserInfoUrl, + "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 + } + } + + oauth2RedirectUrl, err := utils.PostPara(r, "oauth2RedirectUrl") + if err != nil { + utils.SendErrorResponse(w, "oauth2RedirectUrl 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.OAuth2RedirectUrl = oauth2RedirectUrl + 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", "oauth2RedirectUrl", oauth2RedirectUrl) + 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()) + 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 +} diff --git a/src/mod/dynamicproxy/authProviders.go b/src/mod/dynamicproxy/authProviders.go index 8099b55..a296e14 100644 --- a/src/mod/dynamicproxy/authProviders.go +++ b/src/mod/dynamicproxy/authProviders.go @@ -50,6 +50,12 @@ func handleAuthProviderRouting(sep *ProxyEndpoint, w http.ResponseWriter, r *htt h.Parent.Option.Logger.LogHTTPRequest(r, "host-http", 401, requestHostname, "") return true } + } else if sep.AuthenticationProvider.AuthMethod == 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 @@ -116,3 +122,7 @@ func (h *ProxyHandler) handleAutheliaAuth(w http.ResponseWriter, r *http.Request func (h *ProxyHandler) handleAuthentikAuth(w http.ResponseWriter, r *http.Request) error { return h.Parent.Option.AuthentikRouter.HandleAuthentikAuth(w, r) } + +func (h *ProxyHandler) handleOAuth2Auth(w http.ResponseWriter, r *http.Request) error { + return h.Parent.Option.OAuth2Router.HandleOAuth2Auth(w, r) +} diff --git a/src/mod/dynamicproxy/typedef.go b/src/mod/dynamicproxy/typedef.go index 29d20ad..d7fdb9e 100644 --- a/src/mod/dynamicproxy/typedef.go +++ b/src/mod/dynamicproxy/typedef.go @@ -10,6 +10,7 @@ package dynamicproxy import ( _ "embed" "imuslab.com/zoraxy/mod/auth/sso/authentik" + "imuslab.com/zoraxy/mod/auth/sso/oauth2" "net" "net/http" "sync" @@ -66,6 +67,7 @@ type RouterOption struct { /* Authentication Providers */ AutheliaRouter *authelia.AutheliaRouter //Authelia router for Authelia authentication AuthentikRouter *authentik.AuthentikRouter //Authentik router for Authentik authentication + OAuth2Router *oauth2.OAuth2Router //OAuth2Router router for OAuth2Router authentication /* Utilities */ Logger *logger.Logger //Logger for reverse proxy requets @@ -144,7 +146,7 @@ const ( AuthMethodNone AuthMethod = iota //No authentication required AuthMethodBasic //Basic Auth AuthMethodAuthelia //Authelia - AuthMethodOauth2 //Oauth2 + AuthMethodOAuth2 //Oauth2 AuthMethodAuthentik ) diff --git a/src/mod/sshprox/embed.go b/src/mod/sshprox/embed.go index 4c5fe2d..b5ac80d 100644 --- a/src/mod/sshprox/embed.go +++ b/src/mod/sshprox/embed.go @@ -1,14 +1,14 @@ -//go:build (windows && amd64) || (linux && mipsle) || (linux && riscv64) || (freebsd && amd64) -// +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 darwin,arm64 package sshprox 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 ( //go:embed gotty/LICENSE diff --git a/src/reverseproxy.go b/src/reverseproxy.go index d00b606..83e3d21 100644 --- a/src/reverseproxy.go +++ b/src/reverseproxy.go @@ -117,6 +117,7 @@ func ReverseProxtInit() { AccessController: accessController, AutheliaRouter: autheliaRouter, AuthentikRouter: authentikRouter, + OAuth2Router: oauth2Router, LoadBalancer: loadBalancer, PluginManager: pluginManager, /* Utilities */ @@ -587,7 +588,7 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) { } else if authProviderType == 2 { newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodAuthelia } else if authProviderType == 3 { - newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodOauth2 + newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodOAuth2 } else if authProviderType == 4 { newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodAuthentik } else { diff --git a/src/start.go b/src/start.go index 0fadc05..16ab97e 100644 --- a/src/start.go +++ b/src/start.go @@ -1,6 +1,7 @@ package main import ( + "imuslab.com/zoraxy/mod/auth/sso/oauth2" "log" "net/http" "os" @@ -157,6 +158,11 @@ func startupSequence() { Database: sysdb, }) + oauth2Router = oauth2.NewOAuth2Router(&oauth2.OAuth2RouterOptions{ + Logger: SystemWideLogger, + Database: sysdb, + }) + //Create a statistic collector statisticCollector, err = statistic.NewStatisticCollector(statistic.CollectorOption{ Database: sysdb, diff --git a/src/web/components/httprp.html b/src/web/components/httprp.html index 1a9d653..a7afa4e 100644 --- a/src/web/components/httprp.html +++ b/src/web/components/httprp.html @@ -186,7 +186,7 @@ ${subd.AuthenticationProvider.AuthMethod == 0x1?` Basic Auth`:``} ${subd.AuthenticationProvider.AuthMethod == 0x2?` Authelia`:``} - ${subd.AuthenticationProvider.AuthMethod == 0x3?` Oauth2`:``} + ${subd.AuthenticationProvider.AuthMethod == 0x3?` OAuth2`:``} ${subd.AuthenticationProvider.AuthMethod == 0x4?` Authentik`:``} ${subd.AuthenticationProvider.AuthMethod != 0x0 && subd.RequireRateLimit?"
":""} ${subd.RequireRateLimit?` Rate Limit @ ${subd.RateLimit} req/s`:``} @@ -396,6 +396,12 @@ +
+
+ + +
+
diff --git a/src/web/components/sso.html b/src/web/components/sso.html index 820735a..5061ebc 100644 --- a/src/web/components/sso.html +++ b/src/web/components/sso.html @@ -17,10 +17,10 @@

Authelia

Configuration settings for Authelia authentication provider.

-
+
- - + + Example: auth.example.com
@@ -30,7 +30,61 @@ Check this if your authelia server uses HTTPS
- + + +
+
+
+

OAuth 2.0

+

Configuration settings for OAuth 2.0 authentication provider.

+ +
+
+ + + Public identifier of the OAuth2 application +
+
+ + + Secret key of the OAuth2 application +
+
+ + + URL to the OIDC discovery document (usually ending with /.well-known/openid-configuration). Used to automatically fetch provider settings. +
+ +
+ + + 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. +
+ +
+ + + URL used by Zoraxy to exchange a valid OAuth2 authentication code for an access token. Optional if Well-Known url is configured. +
+ +
+ + + URL used by the OAuth2 provider to redirect the user after successful authentication. Should be set to your Zoraxy instance URL +
+ +
+ + + URL used by the OAuth2 provider to validate generated token. Optional if Well-Known url is configured. +
+ +
+ + + 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. +
+
@@ -38,10 +92,10 @@

Authentik

Configuration settings for Authentik authentication provider.

-
+
- - + + Example: auth.example.com
@@ -51,7 +105,7 @@ Check this if your Authentik server uses HTTPS
- +
@@ -83,22 +137,35 @@ console.error('Error fetching SSO settings:', textStatus, errorThrown); } }); + $.cjax({ + url: '/api/sso/OAuth2', + method: 'GET', + dataType: 'json', + success: function(data) { + $('#oauth2WellKnownUrl').val(data.oauth2WellKnownUrl); + $('#oauth2ServerUrl').val(data.oauth2ServerUrl); + $('#oauth2TokenUrl').val(data.oauth2TokenUrl); + $('#oauth2RedirectUrl').val(data.oauth2RedirectUrl); + $('#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); + } + }); }); - function updateAutheliaSettings(){ - var autheliaServerUrl = $('#autheliaServerUrl').val(); - var useHttps = $('#useHttps').prop('checked'); - + $( "#autheliaSettings" ).on( "submit", function( event ) { + event.preventDefault(); $.cjax({ url: '/api/sso/Authelia', method: 'POST', - data: { - autheliaURL: autheliaServerUrl, - useHTTPS: useHttps - }, + data: $( this ).serialize(), success: function(data) { if (data.error != undefined) { - $.msgbox(data.error, false); + msgbox(data.error, false); return; } msgbox('Authelia settings updated', true); @@ -106,23 +173,20 @@ }, error: function(jqXHR, textStatus, errorThrown) { console.error('Error updating Authelia settings:', textStatus, errorThrown); + msgbox('Error updating Authelia settings, check console', false); } }); - } - function updateAuthentikSettings(){ - var authentikServerUrl = $('#authentikServerUrl').val(); - var useHttps = $('#authentikUseHttps').prop('checked'); + }); + $( "#authentikSettings" ).on( "submit", function( event ) { + event.preventDefault(); $.cjax({ url: '/api/sso/Authentik', method: 'POST', - data: { - authentikURL: authentikServerUrl, - useHTTPS: useHttps - }, + data: $( this ).serialize(), success: function(data) { if (data.error != undefined) { - $.msgbox(data.error, false); + msgbox(data.error, false); return; } msgbox('Authentik settings updated', true); @@ -130,7 +194,29 @@ }, error: function(jqXHR, textStatus, errorThrown) { console.error('Error updating Authentik settings:', textStatus, errorThrown); + msgbox('Error updating Authentik settings, check console', false); } }); - } + }); + + $( "#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); + } + }); + }); \ No newline at end of file diff --git a/src/web/index.html b/src/web/index.html index 98ea85b..fb8df92 100644 --- a/src/web/index.html +++ b/src/web/index.html @@ -72,7 +72,7 @@ TLS / SSL certificates - SSO / Oauth + SSO / OAuth2 From e2882b64366567ff1d4584d8348d6373612c3f28 Mon Sep 17 00:00:00 2001 From: Krzysztof Jagosz Date: Tue, 29 Apr 2025 14:46:18 +0200 Subject: [PATCH 2/7] Some cleanup for unused things --- src/mod/auth/sso/oauth2/oauth2.go | 13 +------------ src/web/components/sso.html | 9 +-------- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/src/mod/auth/sso/oauth2/oauth2.go b/src/mod/auth/sso/oauth2/oauth2.go index e036d6f..449c44e 100644 --- a/src/mod/auth/sso/oauth2/oauth2.go +++ b/src/mod/auth/sso/oauth2/oauth2.go @@ -16,7 +16,6 @@ import ( 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 - OAuth2RedirectUrl string //The redirect 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 @@ -56,7 +55,6 @@ func NewOAuth2Router(options *OAuth2RouterOptions) *OAuth2Router { 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", "oauth2RedirectURL", &options.OAuth2RedirectUrl) options.Database.Read("oauth2", "oauth2UserInfoUrl", &options.OAuth2UserInfoUrl) options.Database.Read("oauth2", "oauth2Scopes", &options.OAuth2Scopes) @@ -73,10 +71,9 @@ func (ar *OAuth2Router) HandleSetOAuth2Settings(w http.ResponseWriter, r *http.R "oauth2WellKnownUrl": ar.options.OAuth2WellKnownUrl, "oauth2ServerUrl": ar.options.OAuth2ServerURL, "oauth2TokenUrl": ar.options.OAuth2TokenURL, + "oauth2UserInfoUrl": ar.options.OAuth2UserInfoUrl, "oauth2Scopes": ar.options.OAuth2Scopes, "oauth2ClientSecret": ar.options.OAuth2ClientSecret, - "oauth2RedirectURL": ar.options.OAuth2RedirectUrl, - "oauth2UserInfoURL": ar.options.OAuth2UserInfoUrl, "oauth2ClientId": ar.options.OAuth2ClientId, }) @@ -112,12 +109,6 @@ func (ar *OAuth2Router) HandleSetOAuth2Settings(w http.ResponseWriter, r *http.R } } - oauth2RedirectUrl, err := utils.PostPara(r, "oauth2RedirectUrl") - if err != nil { - utils.SendErrorResponse(w, "oauth2RedirectUrl not found") - return - } - oauth2ClientId, err := utils.PostPara(r, "oauth2ClientId") if err != nil { utils.SendErrorResponse(w, "oauth2ClientId not found") @@ -134,7 +125,6 @@ func (ar *OAuth2Router) HandleSetOAuth2Settings(w http.ResponseWriter, r *http.R ar.options.OAuth2WellKnownUrl = oauth2WellKnownUrl ar.options.OAuth2ServerURL = oauth2ServerUrl ar.options.OAuth2TokenURL = oauth2TokenURL - ar.options.OAuth2RedirectUrl = oauth2RedirectUrl ar.options.OAuth2UserInfoUrl = oauth2UserInfoUrl ar.options.OAuth2ClientId = oauth2ClientId ar.options.OAuth2ClientSecret = oauth2ClientSecret @@ -144,7 +134,6 @@ func (ar *OAuth2Router) HandleSetOAuth2Settings(w http.ResponseWriter, r *http.R 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", "oauth2RedirectUrl", oauth2RedirectUrl) ar.options.Database.Write("oauth2", "oauth2UserInfoUrl", oauth2UserInfoUrl) ar.options.Database.Write("oauth2", "oauth2ClientId", oauth2ClientId) ar.options.Database.Write("oauth2", "oauth2ClientSecret", oauth2ClientSecret) diff --git a/src/web/components/sso.html b/src/web/components/sso.html index 5061ebc..981afea 100644 --- a/src/web/components/sso.html +++ b/src/web/components/sso.html @@ -68,13 +68,7 @@
- - - URL used by the OAuth2 provider to redirect the user after successful authentication. Should be set to your Zoraxy instance URL -
- -
- + URL used by the OAuth2 provider to validate generated token. Optional if Well-Known url is configured.
@@ -145,7 +139,6 @@ $('#oauth2WellKnownUrl').val(data.oauth2WellKnownUrl); $('#oauth2ServerUrl').val(data.oauth2ServerUrl); $('#oauth2TokenUrl').val(data.oauth2TokenUrl); - $('#oauth2RedirectUrl').val(data.oauth2RedirectUrl); $('#oauth2UserInfoUrl').val(data.oauth2UserInfoUrl); $('#oauth2ClientId').val(data.oauth2ClientId); $('#oauth2ClientSecret').val(data.oauth2ClientSecret); From f0fa71c5b4df96a4f87d09d45e3373864277f393 Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Sat, 7 Jun 2025 12:08:45 +0800 Subject: [PATCH 3/7] Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index fabfcb3..d6cb379 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,4 @@ log tmp sys.* www/html/index.html +*.exe \ No newline at end of file From 0e74ff69c3cf838446d44c8fee81b13458ae82a6 Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Sat, 7 Jun 2025 12:20:09 +0800 Subject: [PATCH 4/7] Fixed build error after merge - Fixed buid error in new merge for Oauth2 - Updated version no. - Optimized css in sso page --- src/def.go | 2 +- src/mod/dynamicproxy/authProviders.go | 2 +- src/reverseproxy.go | 2 +- src/web/components/sso.html | 4 +++- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/def.go b/src/def.go index 7d1f616..45a008b 100644 --- a/src/def.go +++ b/src/def.go @@ -44,7 +44,7 @@ import ( const ( /* Build Constants */ SYSTEM_NAME = "Zoraxy" - SYSTEM_VERSION = "3.2.2" + SYSTEM_VERSION = "3.2.3" DEVELOPMENT_BUILD = false /* System Constants */ diff --git a/src/mod/dynamicproxy/authProviders.go b/src/mod/dynamicproxy/authProviders.go index 295add6..cfaafa2 100644 --- a/src/mod/dynamicproxy/authProviders.go +++ b/src/mod/dynamicproxy/authProviders.go @@ -46,7 +46,7 @@ func handleAuthProviderRouting(sep *ProxyEndpoint, w http.ResponseWriter, r *htt h.Parent.Option.Logger.LogHTTPRequest(r, "host-http", 401, requestHostname, "") return true } - } else if sep.AuthenticationProvider.AuthMethod == AuthMethodOAuth2 { + case AuthMethodOauth2: err := h.handleOAuth2Auth(w, r) if err != nil { h.Parent.Option.Logger.LogHTTPRequest(r, "host-http", 401, requestHostname, "") diff --git a/src/reverseproxy.go b/src/reverseproxy.go index 9585de8..abb4e79 100644 --- a/src/reverseproxy.go +++ b/src/reverseproxy.go @@ -587,7 +587,7 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) { } else if authProviderType == 2 { newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodForward } else if authProviderType == 3 { - newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodOAuth2 + newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodOauth2 } else { newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodNone } diff --git a/src/web/components/sso.html b/src/web/components/sso.html index 9327649..605ccdd 100644 --- a/src/web/components/sso.html +++ b/src/web/components/sso.html @@ -1,4 +1,4 @@ -
+

SSO

Single Sign-On (SSO) and authentication providers settings

@@ -253,4 +253,6 @@ }); }); + /* Bind UI events */ + $(".sso .advanceSettings").accordion(); \ No newline at end of file From 1f8684481af50abdefd31c808e132a0616131a5f Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Sun, 8 Jun 2025 21:56:51 +0800 Subject: [PATCH 5/7] New UI for proxy editor --- src/web/components/httprp.html | 640 ++++++++++++++++++++--- src/web/components/httprp.html.bak | 797 +++++++++++++++++++++++++++++ 2 files changed, 1370 insertions(+), 67 deletions(-) create mode 100644 src/web/components/httprp.html.bak diff --git a/src/web/components/httprp.html b/src/web/components/httprp.html index 484eb37..eea543f 100644 --- a/src/web/components/httprp.html +++ b/src/web/components/httprp.html @@ -9,7 +9,7 @@ } .subdEntry td:not(.ignoremw){ - min-width: 200px; + min-width: 100px; } .httpProxyListTools{ @@ -20,10 +20,86 @@ cursor: pointer; } + th.no-sort{ + cursor: default !important; + } + .tag-select:hover{ text-decoration: underline; opacity: 0.8; } + + .inlineEditActionBtn{ + border: 0px solid transparent !important; + box-shadow: none !important; + background-color: transparent !important; + } + + body.darkTheme .ui.basic.small.icon.circular.button.inlineEditActionBtn{ + border: 0px solid transparent !important; + } + + /* Custom, non overlaying modal for proxy rule editing */ + #httprpEditModal{ + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 68vw; + height: 50vh; + background-color: var(--theme_bg_primary); + padding: 1.4em; + border-radius: .6em; + box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2), 0px 8px 16px rgba(0, 0, 0, 0.2); + z-index: 9; + max-width: 840px; + } + + @media screen and (max-width: 1024px) { + #httprpEditModal { + width: 85vw; + } + } + + @media screen and (max-width: 768px) { + #httprpEditModal { + width: 95vw; + height: 80vh; + border-radius: 0; + } + + .httpProxyEditClosePC{ + display:none !important; + } + .httpProxyEditCloseMobile{ + display:block !important; + } + } + + @media screen and (min-width: 769px) { + .httpProxyEditClosePC{ + display:block !important; + } + .httpProxyEditCloseMobile{ + display:none !important; + } + } + + + + #httprpEditDarkenLayer { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + z-index: 8; + } + + #httprpEditModalWrapper { + display: none; /* Hidden by default */ + }
- +
+ - - - + + @@ -76,7 +152,219 @@

+ +
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+
+
+ + +
+
+ + +
+
+ +
+
+ +
+ +
+
+ + +
+
+
+
+ + Advanced Settings +
+
+
+ + +
+
+
+ + +
+
+
+
+
+ +
+
+ +
+
+ +
+
+ Headers + +
+ +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+ +
+ + +

+
+ + +
+
+ +
+ SSL + +
+ +
+ Tags + +
+
+

Note: All changes will be automatically saved.

+

+ +
+
+
+
+
+ + diff --git a/src/web/components/httprp.html.bak b/src/web/components/httprp.html.bak new file mode 100644 index 0000000..484eb37 --- /dev/null +++ b/src/web/components/httprp.html.bak @@ -0,0 +1,797 @@ +
+
+

HTTP Proxy

+

Proxy HTTP server with HTTP or HTTPS for multiple hosts. If you are only proxying for one host / domain, use Default Site instead.

+
+ +
+ +
+ + + +
+
+ +
+
Host Destination Virtual DirectoryTagsAdvanced SettingsActionsTags
+ + + + + + + + + + + + + +
HostDestinationVirtual DirectoryTagsAdvanced SettingsActions
+
+ + +

+
+ + From c7b5e0994e19b68bb99d0e8539d00bb05b71f658 Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Mon, 9 Jun 2025 22:01:30 +0800 Subject: [PATCH 6/7] Added more wip UI elements --- src/web/components/httprp.html | 274 +++++++++++++++++--------- src/web/index.html | 14 ++ src/web/snippet/customHeaders.html | 13 +- src/web/snippet/hostAccessEditor.html | 6 +- src/web/snippet/tagEditor.html | 11 +- 5 files changed, 202 insertions(+), 116 deletions(-) diff --git a/src/web/components/httprp.html b/src/web/components/httprp.html index eea543f..890d8cf 100644 --- a/src/web/components/httprp.html +++ b/src/web/components/httprp.html @@ -46,7 +46,7 @@ left: 50%; transform: translate(-50%, -50%); width: 68vw; - height: 50vh; + height: 70vh; background-color: var(--theme_bg_primary); padding: 1.4em; border-radius: .6em; @@ -55,6 +55,44 @@ max-width: 840px; } + #httprpEditModal .rpconfig_content{ + height: 100%; + } + + #httprpEditModal .editor_side_wrapper{ + position: absolute; + top: 0; + right: 0; + height:100%; + width: 100%; + background-color: var(--theme_bg_primary); + } + + #httprpEditModal .wrapper_frame{ + width: 100%; + border: 1px solid var(--divider_color); + border-radius: 0.5em; + height: calc(100%); + } + + #httprpEditModal .editor_side_wrapper .wrapper_frame{ + height: calc(100% - 30px); + } + + #httprpEditModal .editor_back_button { + border: none; + background-color: transparent; + box-shadow: none; + color: var(--text_color); + font-size: 2.2em; + cursor: pointer; + padding-top: 20px; + } + + #httprpEditModal .editor_back_button:hover{ + opacity: 0.5; + } + @media screen and (max-width: 1024px) { #httprpEditModal { width: 85vw; @@ -63,9 +101,9 @@ @media screen and (max-width: 768px) { #httprpEditModal { - width: 95vw; height: 80vh; border-radius: 0; + overflow-y: scroll; } .httpProxyEditClosePC{ @@ -85,8 +123,6 @@ } } - - #httprpEditDarkenLayer { position: fixed; top: 0; @@ -95,6 +131,7 @@ height: 100%; background-color: rgba(0, 0, 0, 0.5); z-index: 8; + backdrop-filter: blur(3px); } #httprpEditModalWrapper { @@ -155,7 +192,7 @@
-
+
-
+
-

- -

-
+
+

+ +

+
-
-
-
-
- -
-
- - +
+
+
+ + +
+
+ + +
-
+
+
-
- -
-
- - -
-
-
-
- - Advanced Settings -
-
-
- - +
+ +
+
+ + +
+
+
+
+ + Advanced Settings
-
-
- - +
+
+ + +
+
+
+ + +
@@ -241,17 +282,21 @@
-
+
+
+
+
+
-
-
+
- Headers - +
@@ -303,17 +348,25 @@
SSL - +
- Tags - +
-

Note: All changes will be automatically saved.

-

+ +
@@ -366,6 +419,17 @@ return vdList; } + function renderTagList(subd){ + let tagList = ""; + if (subd.Tags.length > 0){ + tagList = subd.Tags.map(tag => `${tag}`).join(""); + }else{ + tagList = "No Tags"; + tagListEmpty = true; + } + return tagList; + } + /* List all proxy endpoints */ function listProxyEndpoints(){ $.get("/api/proxy/list?type=host", function(data){ @@ -434,14 +498,8 @@ } //Build tag list - let tagList = ""; - let tagListEmpty = false; - if (subd.Tags.length > 0){ - tagList = subd.Tags.map(tag => `${tag}`).join(""); - }else{ - tagList = "No Tags"; - tagListEmpty = true; - } + let tagList = renderTagList(subd); + let tagListEmpty = (subd.Tags.length == 0); $("#httpProxyList").append(` @@ -879,7 +937,7 @@ ept: "host", ep: uuid })); - showSideWrapper("snippet/basicAuthEditor.html?t=" + Date.now() + "#" + payload); + showEditorSideWrapper("snippet/basicAuthEditor.html?t=" + Date.now() + "#" + payload); } function editAccessRule(uuid){ @@ -887,7 +945,7 @@ ept: "host", ep: uuid })); - showSideWrapper("snippet/hostAccessEditor.html?t=" + Date.now() + "#" + payload); + showEditorSideWrapper("snippet/hostAccessEditor.html?t=" + Date.now() + "#" + payload); } function editAliasHostnames(uuid){ @@ -895,7 +953,7 @@ ept: "host", ep: uuid })); - showSideWrapper("snippet/aliasEditor.html?t=" + Date.now() + "#" + payload); + showEditorSideWrapper("snippet/aliasEditor.html?t=" + Date.now() + "#" + payload); } function quickEditVdir(uuid){ @@ -903,22 +961,13 @@ $("#vdirBaseRoutingRule").parent().dropdown("set selected", uuid); } - //Open the custom header editor - function editCustomHeaders(uuid){ - let payload = encodeURIComponent(JSON.stringify({ - ept: "host", - ep: uuid - })); - showSideWrapper("snippet/customHeaders.html?t=" + Date.now() + "#" + payload); - } - //Open the load balance option function editUpstreams(uuid){ let payload = encodeURIComponent(JSON.stringify({ ept: "host", ep: uuid })); - showSideWrapper("snippet/upstreams.html?t=" + Date.now() + "#" + payload); + showEditorSideWrapper("snippet/upstreams.html?t=" + Date.now() + "#" + payload); } function handleProxyRuleToggle(object){ @@ -1048,10 +1097,10 @@ ept: "host", ep: uuid })); - showSideWrapper("snippet/tagEditor.html?t=" + Date.now() + "#" + payload); + showEditorSideWrapper("snippet/tagEditor.html?t=" + Date.now() + "#" + payload); } - // Render the tags preview from tag editing snippet + // Render the tags preview from tag editing snippet, callback from tags editor function renderTagsPreview(endpoint, tags){ let targetProxyRuleEle = $(".subdEntry[eptuuid='" + endpoint + "'] td[data-label='tags']"); //Update the tag DOM @@ -1062,9 +1111,8 @@ $(targetProxyRuleEle).attr("payload", encodeURIComponent(JSON.stringify(tags))); } + function getTagsArrayFromEndpoint(endpoint){ - console.log("wip"); - return []; let targetProxyRuleEle = $(".subdEntry[eptuuid='" + endpoint + "'] td[data-label='tags']"); let tags = $(targetProxyRuleEle).attr("payload"); return JSON.parse(decodeURIComponent(tags)); @@ -1076,8 +1124,12 @@ }); function closeHttpRuleEditor(){ - $("#httprpEditModalWrapper").hide(); - exitProxyInlineEdit(); + $(".ui.toggle.tiny.fitted.checkbox").css("z-index", 0); + $("#httprpEditModalWrapper").fadeOut("fast", function(){ + $(".ui.toggle.tiny.fitted.checkbox").css("z-index", "auto"); + $("body").css("overflow", "auto"); + exitProxyInlineEdit(); + }); } //Get the current editing http hostname, return null if errors @@ -1093,7 +1145,7 @@ //Initialize the http proxy rule editor function initHttpProxyRuleEditorModal(rulepayload){ let subd = JSON.parse(JSON.stringify(rulepayload)); - + //Populate all the information in the proxy editor populateAndBindEventsToHTTPProxyEditor(subd); @@ -1102,7 +1154,13 @@ $("#httprpEditModal .rpconfig_content[rpcfg='downstream']").show(); $("#httprpEditModal .hrpedit_menu_item.active").removeClass("active"); $("#httprpEditModal .hrpedit_menu_item[cfgpage='downstream']").addClass("active"); - $("#httprpEditModalWrapper").show(); + $("body").css("overflow", "hidden"); + // Fixing a bug in semantic ui that when an element fade in/out on top of checkbox + // the checkbox suddently flash on top of the fading element + $(".ui.toggle.tiny.fitted.checkbox").css("z-index", 0); + $("#httprpEditModalWrapper").fadeIn("fast", function(){ + $(".ui.toggle.tiny.fitted.checkbox").css("z-index", "auto"); + }); } // This function populate the bind all the events required for the proxy editor @@ -1195,9 +1253,14 @@ }); /* Headers */ - editor.find(".editHeaderBtn").off("click").on("click", function(){ - editCustomHeaders(uuid); - }); + (() => { + let payload = encodeURIComponent(JSON.stringify({ + ept: "host", + ep: uuid + })); + let frameURL = "snippet/customHeaders.html?t=" + Date.now() + "#" + payload; + editor.find(".rpconfig_content[rpcfg='headers'] .wrapper_frame").attr('src', frameURL); + })(); /* ------------ Security ------------ */ @@ -1242,9 +1305,14 @@ /* ------------ TLS ------------ */ /* ------------ Tags ------------ */ - editor.find(".editTagsBtn").off("click").on("click", function(){ - editTags(uuid); - }); + (()=>{ + let payload = encodeURIComponent(JSON.stringify({ + ept: "host", + ep: uuid + })); + let frameURL = "snippet/tagEditor.html?t=" + Date.now() + "#" + payload; + editor.find(".rpconfig_content[rpcfg='tags'] .wrapper_frame").attr('src', frameURL); + })(); console.log(subd); } @@ -1273,8 +1341,30 @@ let cfgPageId = $(this).attr("cfgpage"); $("#httprpEditModal .rpconfig_content").hide(); $(`#httprpEditModal .rpconfig_content[rpcfg='${cfgPageId}']`).show(); + hideEditorSideWrapper(); //Always close the side wrapper on tab change }); + $("#httprpEditModal .editor_back_button").on("click", function(event) { + // Prevent click event from propagating to the modal background + event.stopPropagation(); + hideEditorSideWrapper(); + }); + + function showEditorSideWrapper(url){ + $("#httprpEditModal .editor_side_wrapper .wrapper_frame").attr('src', url); + $("#httprpEditModal .editor_side_wrapper").fadeIn("fast"); + } + + function hideEditorSideWrapper(){ + $("#httprpEditModal .editor_side_wrapper").fadeOut("fast"); + $("#httprpEditModal .editor_side_wrapper .wrapper_frame").attr('src', "snippet/placeholder.html"); + } + + + /* + Page Initialization Functions + */ + // Initialize the proxy list on page load $(document).ready(function() { listProxyEndpoints(); diff --git a/src/web/index.html b/src/web/index.html index fb8df92..ac3480a 100644 --- a/src/web/index.html +++ b/src/web/index.html @@ -334,6 +334,7 @@ } function toggleTheme(){ + let editorSideWrapper = $("#httprpEditModal .wrapper_frame"); if ($("body").hasClass("darkTheme")){ setDarkTheme(false); //Check if the snippet iframe is opened. If yes, set the dark theme to the iframe @@ -341,6 +342,10 @@ $(".sideWrapper iframe")[0].contentWindow.setDarkTheme(false); } + if (editorSideWrapper.is(":visible")){ + editorSideWrapper[0].contentWindow.setDarkTheme(false); + } + if ($("#pluginContextLoader").is(":visible")){ $("#pluginContextLoader")[0].contentWindow.setDarkTheme(false); } @@ -350,6 +355,9 @@ if ($(".sideWrapper").is(":visible")){ $(".sideWrapper iframe")[0].contentWindow.setDarkTheme(true); } + if (editorSideWrapper.is(":visible")){ + editorSideWrapper[0].contentWindow.setDarkTheme(true); + } if ($("#pluginContextLoader").is(":visible")){ $("#pluginContextLoader")[0].contentWindow.setDarkTheme(true); } @@ -515,6 +523,12 @@ } 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){ $(".sideWrapper iframe").attr("src", "snippet/placeholder.html"); } diff --git a/src/web/snippet/customHeaders.html b/src/web/snippet/customHeaders.html index e7fcd4b..4944da9 100644 --- a/src/web/snippet/customHeaders.html +++ b/src/web/snippet/customHeaders.html @@ -34,13 +34,6 @@
-
-
- Custom Headers -
-
-
-
- -
- -




@@ -189,7 +178,7 @@ let payloadHash = window.location.hash.substr(1); try{ payloadHash = JSON.parse(decodeURIComponent(payloadHash)); - $("#epname").text(payloadHash.ep); + //$("#epname").text(payloadHash.ep); editingEndpoint = payloadHash; }catch(ex){ console.log("Unable to load endpoint data from hash") diff --git a/src/web/snippet/hostAccessEditor.html b/src/web/snippet/hostAccessEditor.html index 474775e..329e848 100644 --- a/src/web/snippet/hostAccessEditor.html +++ b/src/web/snippet/hostAccessEditor.html @@ -67,8 +67,10 @@
- Host Access Settings -
+
+ Host Access Settings +
+
diff --git a/src/web/snippet/tagEditor.html b/src/web/snippet/tagEditor.html index 853207d..d5801fb 100644 --- a/src/web/snippet/tagEditor.html +++ b/src/web/snippet/tagEditor.html @@ -21,13 +21,6 @@
-
-
- Edit Tags -
-
-
-

Tags currently applied to this host name / proxy rule

@@ -68,9 +61,7 @@ -
- - +


+

Enter alias hostname or wildcard matching keywords for

@@ -50,10 +51,6 @@
-
-
- -




diff --git a/src/web/snippet/customHeaders.html b/src/web/snippet/customHeaders.html index 4944da9..fe2af1e 100644 --- a/src/web/snippet/customHeaders.html +++ b/src/web/snippet/customHeaders.html @@ -27,6 +27,11 @@ body.darkTheme #permissionPolicyEditor .experimental{ background-color: rgb(41, 41, 41); } + + .advanceoptions{ + background: var(--theme_advance) !important; + border-radius: 0.4em !important; + } diff --git a/src/web/snippet/hostAccessEditor.html b/src/web/snippet/hostAccessEditor.html index 329e848..6413b30 100644 --- a/src/web/snippet/hostAccessEditor.html +++ b/src/web/snippet/hostAccessEditor.html @@ -35,7 +35,7 @@ #accessRuleList{ padding: 0.6em; - border: 1px solid rgb(228, 228, 228); + /* border: 1px solid rgb(228, 228, 228); */ border-radius: 0.4em !important; max-height: calc(100vh - 15em); min-height: 300px; @@ -65,15 +65,6 @@
-
-
-
- Host Access Settings -
-
-
-
-

Select an access rule to apply blacklist / whitelist filtering

@@ -87,9 +78,7 @@

- - - +


@@ -176,6 +165,35 @@ let accessRuleID = $(accessRuleObject).attr("ruleid"); $(".accessRule").removeClass('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(){ diff --git a/src/web/snippet/upstreams.html b/src/web/snippet/upstreams.html index 285d084..779d763 100644 --- a/src/web/snippet/upstreams.html +++ b/src/web/snippet/upstreams.html @@ -75,13 +75,6 @@
-
-
- Upstreams / Load Balance -
-
-
-
-
-
- -