mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-21 06:53:04 +02:00
Merge branch 'main' of https://github.com/kjagosz/zoraxy into v3.2.3
This commit is contained in:
commit
8cb47e19fa
6
.gitignore
vendored
6
.gitignore
vendored
@ -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
|
||||||
|
@ -83,6 +83,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
|
||||||
|
@ -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"
|
||||||
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
286
src/mod/auth/sso/oauth2/oauth2.go
Normal file
286
src/mod/auth/sso/oauth2/oauth2.go
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
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
|
||||||
|
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())
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
} 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
|
||||||
@ -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)
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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 */
|
||||||
@ -586,7 +587,7 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
} else if authProviderType == 2 {
|
} else if authProviderType == 2 {
|
||||||
newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodForward
|
newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodForward
|
||||||
} else if authProviderType == 3 {
|
} else if authProviderType == 3 {
|
||||||
newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodOauth2
|
newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodOAuth2
|
||||||
} else {
|
} else {
|
||||||
newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodNone
|
newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodNone
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -395,6 +395,12 @@
|
|||||||
<label>Forward Auth</label>
|
<label>Forward Auth</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>
|
</div>
|
||||||
<button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editBasicAuthCredentials('${uuid}');"><i class="ui blue user circle icon"></i> Edit Credentials</button>
|
<button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editBasicAuthCredentials('${uuid}');"><i class="ui blue user circle icon"></i> Edit Credentials</button>
|
||||||
<button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editCustomHeaders('${uuid}');"><i class="heading icon"></i> Custom Headers</button>
|
<button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editCustomHeaders('${uuid}');"><i class="heading icon"></i> Custom Headers</button>
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
<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>
|
||||||
</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">
|
||||||
@ -66,7 +66,55 @@
|
|||||||
</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>
|
||||||
|
</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="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>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
@ -74,6 +122,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
$(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',
|
||||||
@ -89,8 +138,33 @@
|
|||||||
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();
|
||||||
@ -123,4 +197,60 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$("#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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
@ -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">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user