Added OAuth2 support for SSO

This commit is contained in:
Krzysztof Jagosz 2025-04-29 01:05:48 +02:00
parent 0e5550487e
commit 61b873451f
14 changed files with 454 additions and 37 deletions

6
.gitignore vendored
View File

@ -50,3 +50,9 @@ 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

View File

@ -84,6 +84,7 @@ func RegisterTLSAPIs(authRouter *auth.RouterDef) {
func RegisterAuthenticationHandlerAPIs(authRouter *auth.RouterDef) { func RegisterAuthenticationHandlerAPIs(authRouter *auth.RouterDef) {
authRouter.HandleFunc("/api/sso/Authelia", autheliaRouter.HandleSetAutheliaURLAndHTTPS) authRouter.HandleFunc("/api/sso/Authelia", autheliaRouter.HandleSetAutheliaURLAndHTTPS)
authRouter.HandleFunc("/api/sso/Authentik", authentikRouter.HandleSetAuthentikURLAndHTTPS) authRouter.HandleFunc("/api/sso/Authentik", authentikRouter.HandleSetAuthentikURLAndHTTPS)
authRouter.HandleFunc("/api/sso/OAuth2", oauth2Router.HandleSetOAuth2Settings)
} }
// Register the APIs for redirection rules management functions // Register the APIs for redirection rules management functions

View File

@ -10,6 +10,7 @@ package main
import ( import (
"embed" "embed"
"flag" "flag"
"imuslab.com/zoraxy/mod/auth/sso/oauth2"
"net/http" "net/http"
"time" "time"
@ -146,6 +147,7 @@ var (
//Authentication Provider //Authentication Provider
autheliaRouter *authelia.AutheliaRouter //Authelia router for Authelia authentication autheliaRouter *authelia.AutheliaRouter //Authelia router for Authelia authentication
authentikRouter *authentik.AuthentikRouter //Authentik router for Authentik authentication authentikRouter *authentik.AuthentikRouter //Authentik router for Authentik 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

View File

@ -16,6 +16,7 @@ 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/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
@ -32,7 +33,6 @@ require (
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/golang/snappy v0.0.1 // indirect github.com/golang/snappy v0.0.1 // indirect
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.128 // 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/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
github.com/peterhellberg/link v1.2.0 // indirect github.com/peterhellberg/link v1.2.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect

View File

@ -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
} }

View File

@ -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
}

View File

@ -50,6 +50,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
} }
} 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 //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 { func (h *ProxyHandler) handleAuthentikAuth(w http.ResponseWriter, r *http.Request) error {
return h.Parent.Option.AuthentikRouter.HandleAuthentikAuth(w, r) 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)
}

View File

@ -10,6 +10,7 @@ package dynamicproxy
import ( import (
_ "embed" _ "embed"
"imuslab.com/zoraxy/mod/auth/sso/authentik" "imuslab.com/zoraxy/mod/auth/sso/authentik"
"imuslab.com/zoraxy/mod/auth/sso/oauth2"
"net" "net"
"net/http" "net/http"
"sync" "sync"
@ -66,6 +67,7 @@ type RouterOption struct {
/* Authentication Providers */ /* Authentication Providers */
AutheliaRouter *authelia.AutheliaRouter //Authelia router for Authelia authentication AutheliaRouter *authelia.AutheliaRouter //Authelia router for Authelia authentication
AuthentikRouter *authentik.AuthentikRouter //Authentik router for Authentik authentication AuthentikRouter *authentik.AuthentikRouter //Authentik router for Authentik authentication
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
@ -144,7 +146,7 @@ const (
AuthMethodNone AuthMethod = iota //No authentication required AuthMethodNone AuthMethod = iota //No authentication required
AuthMethodBasic //Basic Auth AuthMethodBasic //Basic Auth
AuthMethodAuthelia //Authelia AuthMethodAuthelia //Authelia
AuthMethodOauth2 //Oauth2 AuthMethodOAuth2 //Oauth2
AuthMethodAuthentik AuthMethodAuthentik
) )

View File

@ -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

View File

@ -117,6 +117,7 @@ func ReverseProxtInit() {
AccessController: accessController, AccessController: accessController,
AutheliaRouter: autheliaRouter, AutheliaRouter: autheliaRouter,
AuthentikRouter: authentikRouter, AuthentikRouter: authentikRouter,
OAuth2Router: oauth2Router,
LoadBalancer: loadBalancer, LoadBalancer: loadBalancer,
PluginManager: pluginManager, PluginManager: pluginManager,
/* Utilities */ /* Utilities */
@ -587,7 +588,7 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
} else if authProviderType == 2 { } else if authProviderType == 2 {
newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodAuthelia newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodAuthelia
} else if authProviderType == 3 { } else if authProviderType == 3 {
newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodOauth2 newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodOAuth2
} else if authProviderType == 4 { } else if authProviderType == 4 {
newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodAuthentik newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodAuthentik
} else { } else {

View File

@ -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"
@ -157,6 +158,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,

View File

@ -186,7 +186,7 @@
<td data-label="" editable="true" datatype="advanced" style="width: 350px;"> <td data-label="" editable="true" datatype="advanced" style="width: 350px;">
${subd.AuthenticationProvider.AuthMethod == 0x1?`<i class="ui grey key icon"></i> Basic Auth`:``} ${subd.AuthenticationProvider.AuthMethod == 0x1?`<i class="ui grey key icon"></i> Basic Auth`:``}
${subd.AuthenticationProvider.AuthMethod == 0x2?`<i class="ui blue key icon"></i> Authelia`:``} ${subd.AuthenticationProvider.AuthMethod == 0x2?`<i class="ui blue key icon"></i> Authelia`:``}
${subd.AuthenticationProvider.AuthMethod == 0x3?`<i class="ui yellow key icon"></i> Oauth2`:``} ${subd.AuthenticationProvider.AuthMethod == 0x3?`<i class="ui yellow key icon"></i> OAuth2`:``}
${subd.AuthenticationProvider.AuthMethod == 0x4?`<i class="ui blue key icon"></i> Authentik`:``} ${subd.AuthenticationProvider.AuthMethod == 0x4?`<i class="ui blue key icon"></i> Authentik`:``}
${subd.AuthenticationProvider.AuthMethod != 0x0 && subd.RequireRateLimit?"<br>":""} ${subd.AuthenticationProvider.AuthMethod != 0x0 && subd.RequireRateLimit?"<br>":""}
${subd.RequireRateLimit?`<i class="ui green check icon"></i> Rate Limit @ ${subd.RateLimit} req/s`:``} ${subd.RequireRateLimit?`<i class="ui green check icon"></i> Rate Limit @ ${subd.RateLimit} req/s`:``}
@ -396,6 +396,12 @@
<label>Authelia</label> <label>Authelia</label>
</div> </div>
</div> </div>
<div class="field">
<div class="ui radio checkbox">
<input type="radio" value="3" name="authProviderType" ${authProvider==0x3?"checked":""}>
<label>OAuth2</label>
</div>
</div>
<div class="field"> <div class="field">
<div class="ui radio checkbox"> <div class="ui radio checkbox">
<input type="radio" value="4" name="authProviderType" ${authProvider==0x4?"checked":""}> <input type="radio" value="4" name="authProviderType" ${authProvider==0x4?"checked":""}>

View File

@ -17,10 +17,10 @@
<h3>Authelia</h3> <h3>Authelia</h3>
<p>Configuration settings for Authelia authentication provider.</p> <p>Configuration settings for Authelia authentication provider.</p>
<form class="ui form"> <form class="ui form" action="#" id="autheliaSettings">
<div class="field"> <div class="field">
<label for="autheliaServerUrl">Authelia Server URL</label> <label for="autheliaURL">Authelia Server URL</label>
<input type="text" id="autheliaServerUrl" name="autheliaServerUrl" placeholder="Enter Authelia Server URL"> <input type="text" id="autheliaURL" name="autheliaURL" placeholder="Enter Authelia Server URL">
<small>Example: auth.example.com</small> <small>Example: auth.example.com</small>
</div> </div>
<div class="field"> <div class="field">
@ -30,7 +30,61 @@
<small>Check this if your authelia server uses HTTPS</small> <small>Check this if your authelia server uses HTTPS</small>
</div> </div>
</div> </div>
<button class="ui basic button" onclick="event.preventDefault(); updateAutheliaSettings();"><i class="green check icon"></i> Apply Change</button> <button class="ui basic button"><i class="green check icon"></i> Apply Change</button>
</form>
</div>
<div class="ui divider"></div>
<div class="ui basic segment">
<h3>OAuth 2.0</h3>
<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="oauth2RedirectUrl">Redirect URL</label>
<input type="text" id="oauth2RedirectUrl" name="oauth2RedirectUrl" placeholder="Enter Token URL">
<small>URL used by the OAuth2 provider to redirect the user after successful authentication. Should be set to your Zoraxy instance URL</small>
</div>
<div class="field">
<label for="oauth2UserInfoURL">Redirect 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> </form>
</div> </div>
<div class="ui divider"></div> <div class="ui divider"></div>
@ -38,10 +92,10 @@
<h3>Authentik</h3> <h3>Authentik</h3>
<p>Configuration settings for Authentik authentication provider.</p> <p>Configuration settings for Authentik authentication provider.</p>
<form class="ui form"> <form class="ui form" action="#" id="authentikSettings">
<div class="field"> <div class="field">
<label for="authentikServerUrl">Authentik Server URL</label> <label for="authentikURL">Authentik Server URL</label>
<input type="text" id="authentikServerUrl" name="authentikServerUrl" placeholder="Enter Authentik Server URL"> <input type="text" id="authentikURL" name="authentikURL" placeholder="Enter Authentik Server URL">
<small>Example: auth.example.com</small> <small>Example: auth.example.com</small>
</div> </div>
<div class="field"> <div class="field">
@ -51,7 +105,7 @@
<small>Check this if your Authentik server uses HTTPS</small> <small>Check this if your Authentik server uses HTTPS</small>
</div> </div>
</div> </div>
<button class="ui basic button" onclick="event.preventDefault(); updateAuthentikSettings();"><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 divider"></div>
@ -83,22 +137,35 @@
console.error('Error fetching SSO settings:', textStatus, errorThrown); 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(){ $( "#autheliaSettings" ).on( "submit", function( event ) {
var autheliaServerUrl = $('#autheliaServerUrl').val(); event.preventDefault();
var useHttps = $('#useHttps').prop('checked');
$.cjax({ $.cjax({
url: '/api/sso/Authelia', url: '/api/sso/Authelia',
method: 'POST', method: 'POST',
data: { data: $( this ).serialize(),
autheliaURL: autheliaServerUrl,
useHTTPS: useHttps
},
success: function(data) { success: function(data) {
if (data.error != undefined) { if (data.error != undefined) {
$.msgbox(data.error, false); msgbox(data.error, false);
return; return;
} }
msgbox('Authelia settings updated', true); msgbox('Authelia settings updated', true);
@ -106,23 +173,20 @@
}, },
error: function(jqXHR, textStatus, errorThrown) { error: function(jqXHR, textStatus, errorThrown) {
console.error('Error updating Authelia settings:', 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({ $.cjax({
url: '/api/sso/Authentik', url: '/api/sso/Authentik',
method: 'POST', method: 'POST',
data: { data: $( this ).serialize(),
authentikURL: authentikServerUrl,
useHTTPS: useHttps
},
success: function(data) { success: function(data) {
if (data.error != undefined) { if (data.error != undefined) {
$.msgbox(data.error, false); msgbox(data.error, false);
return; return;
} }
msgbox('Authentik settings updated', true); msgbox('Authentik settings updated', true);
@ -130,7 +194,29 @@
}, },
error: function(jqXHR, textStatus, errorThrown) { error: function(jqXHR, textStatus, errorThrown) {
console.error('Error updating Authentik settings:', 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);
}
});
});
</script> </script>

View File

@ -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">