mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-03 06:07:20 +02:00
Fixed #316
Fixed early renew day not passed into auto renewer config bug
This commit is contained in:
parent
5c56da1180
commit
cab2f4e63a
@ -97,11 +97,13 @@ func initAPIs(targetMux *http.ServeMux) {
|
|||||||
|
|
||||||
//SSO and Oauth
|
//SSO and Oauth
|
||||||
authRouter.HandleFunc("/api/sso/status", ssoHandler.HandleSSOStatus)
|
authRouter.HandleFunc("/api/sso/status", ssoHandler.HandleSSOStatus)
|
||||||
authRouter.HandleFunc("/api/sso/start", ssoHandler.HandleStartSSOPortal)
|
authRouter.HandleFunc("/api/sso/enable", ssoHandler.HandleSSOEnable)
|
||||||
authRouter.HandleFunc("/api/sso/stop", ssoHandler.HandleStopSSOPortal)
|
|
||||||
authRouter.HandleFunc("/api/sso/setPort", ssoHandler.HandlePortChange)
|
authRouter.HandleFunc("/api/sso/setPort", ssoHandler.HandlePortChange)
|
||||||
authRouter.HandleFunc("/api/sso/setAuthURL", ssoHandler.HandleSetAuthURL)
|
authRouter.HandleFunc("/api/sso/setAuthURL", ssoHandler.HandleSetAuthURL)
|
||||||
//authRouter.HandleFunc("/api/sso/registerApp", ssoHandler.HandleRegisterApp)
|
|
||||||
|
authRouter.HandleFunc("/api/sso/app/register", ssoHandler.HandleRegisterApp)
|
||||||
|
//authRouter.HandleFunc("/api/sso/app/list", ssoHandler.HandleListApp)
|
||||||
|
//authRouter.HandleFunc("/api/sso/app/remove", ssoHandler.HandleRemoveApp)
|
||||||
|
|
||||||
authRouter.HandleFunc("/api/sso/user/list", ssoHandler.HandleListUser)
|
authRouter.HandleFunc("/api/sso/user/list", ssoHandler.HandleListUser)
|
||||||
authRouter.HandleFunc("/api/sso/user/add", ssoHandler.HandleAddUser)
|
authRouter.HandleFunc("/api/sso/user/add", ssoHandler.HandleAddUser)
|
||||||
|
@ -88,9 +88,12 @@ func NewAutoRenewer(config string, certFolder string, renewCheckInterval int64,
|
|||||||
AcmeHandler: AcmeHandler,
|
AcmeHandler: AcmeHandler,
|
||||||
RenewerConfig: &renewerConfig,
|
RenewerConfig: &renewerConfig,
|
||||||
RenewTickInterval: renewCheckInterval,
|
RenewTickInterval: renewCheckInterval,
|
||||||
|
EarlyRenewDays: earlyRenewDays,
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
thisRenewer.Logf("ACME early renew set to "+fmt.Sprint(earlyRenewDays)+" days and check interval set to "+fmt.Sprint(renewCheckInterval)+" seconds", nil)
|
||||||
|
|
||||||
if thisRenewer.RenewerConfig.Enabled {
|
if thisRenewer.RenewerConfig.Enabled {
|
||||||
//Start the renew ticker
|
//Start the renew ticker
|
||||||
thisRenewer.StartAutoRenewTicker()
|
thisRenewer.StartAutoRenewTicker()
|
||||||
@ -103,7 +106,7 @@ func NewAutoRenewer(config string, certFolder string, renewCheckInterval int64,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *AutoRenewer) Logf(message string, err error) {
|
func (a *AutoRenewer) Logf(message string, err error) {
|
||||||
a.Logger.PrintAndLog("CertRenew", message, err)
|
a.Logger.PrintAndLog("cert-renew", message, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AutoRenewer) StartAutoRenewTicker() {
|
func (a *AutoRenewer) StartAutoRenewTicker() {
|
||||||
|
@ -5,14 +5,14 @@ import (
|
|||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Get the issuer name from pem file
|
// Get the issuer name from pem file
|
||||||
func ExtractIssuerNameFromPEM(pemFilePath string) (string, error) {
|
func ExtractIssuerNameFromPEM(pemFilePath string) (string, error) {
|
||||||
// Read the PEM file
|
// Read the PEM file
|
||||||
pemData, err := ioutil.ReadFile(pemFilePath)
|
pemData, err := os.ReadFile(pemFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -37,16 +37,46 @@ func (s *SSOHandler) HandleSSOStatus(w http.ResponseWriter, r *http.Request) {
|
|||||||
utils.SendJSONResponse(w, string(js))
|
utils.SendJSONResponse(w, string(js))
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleStartSSOPortal handle the request to start the SSO portal server
|
// Wrapper for starting and stopping the SSO portal server
|
||||||
func (s *SSOHandler) HandleStartSSOPortal(w http.ResponseWriter, r *http.Request) {
|
// require POST request with key "enable" and value "true" or "false"
|
||||||
err := s.StartSSOPortal()
|
func (s *SSOHandler) HandleSSOEnable(w http.ResponseWriter, r *http.Request) {
|
||||||
|
enable, err := utils.PostBool(r, "enable")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log("Failed to start SSO portal server", err)
|
utils.SendErrorResponse(w, "invalid enable value")
|
||||||
utils.SendErrorResponse(w, "failed to start SSO portal server")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if enable {
|
||||||
|
s.HandleStartSSOPortal(w, r)
|
||||||
|
} else {
|
||||||
|
s.HandleStopSSOPortal(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleStartSSOPortal handle the request to start the SSO portal server
|
||||||
|
func (s *SSOHandler) HandleStartSSOPortal(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if s.ssoPortalServer != nil {
|
||||||
|
//Already enabled. Do restart instead.
|
||||||
|
err := s.RestartSSOServer()
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "failed to start SSO server")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
utils.SendOK(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check if the authURL is set correctly. If not, return error
|
||||||
|
if s.Config.AuthURL == "" {
|
||||||
|
utils.SendErrorResponse(w, "auth URL not set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Start the SSO portal server in go routine
|
||||||
|
go s.StartSSOPortal()
|
||||||
|
|
||||||
//Write current state to database
|
//Write current state to database
|
||||||
err = s.Config.Database.Write("sso_conf", "enabled", true)
|
err := s.Config.Database.Write("sso_conf", "enabled", true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.SendErrorResponse(w, "failed to update SSO state")
|
utils.SendErrorResponse(w, "failed to update SSO state")
|
||||||
return
|
return
|
||||||
@ -56,6 +86,12 @@ func (s *SSOHandler) HandleStartSSOPortal(w http.ResponseWriter, r *http.Request
|
|||||||
|
|
||||||
// HandleStopSSOPortal handle the request to stop the SSO portal server
|
// HandleStopSSOPortal handle the request to stop the SSO portal server
|
||||||
func (s *SSOHandler) HandleStopSSOPortal(w http.ResponseWriter, r *http.Request) {
|
func (s *SSOHandler) HandleStopSSOPortal(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if s.ssoPortalServer == nil {
|
||||||
|
//Already disabled
|
||||||
|
utils.SendOK(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
err := s.ssoPortalServer.Close()
|
err := s.ssoPortalServer.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log("Failed to stop SSO portal server", err)
|
s.Log("Failed to stop SSO portal server", err)
|
||||||
@ -70,13 +106,6 @@ func (s *SSOHandler) HandleStopSSOPortal(w http.ResponseWriter, r *http.Request)
|
|||||||
utils.SendErrorResponse(w, "failed to update SSO state")
|
utils.SendErrorResponse(w, "failed to update SSO state")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//Clear the cookie store and restart the server
|
|
||||||
err = s.RestartSSOServer()
|
|
||||||
if err != nil {
|
|
||||||
utils.SendErrorResponse(w, "failed to restart SSO server")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,11 +133,13 @@ func (s *SSOHandler) HandlePortChange(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//Clear the cookie store and restart the server
|
if s.IsRunning() {
|
||||||
err = s.RestartSSOServer()
|
//Restart the server if it is running
|
||||||
if err != nil {
|
err = s.RestartSSOServer()
|
||||||
utils.SendErrorResponse(w, "failed to restart SSO server")
|
if err != nil {
|
||||||
return
|
utils.SendErrorResponse(w, "failed to restart SSO server")
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
|
58
src/mod/auth/sso/openid.go
Normal file
58
src/mod/auth/sso/openid.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package sso
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OpenIDConfiguration struct {
|
||||||
|
Issuer string `json:"issuer"`
|
||||||
|
AuthorizationEndpoint string `json:"authorization_endpoint"`
|
||||||
|
TokenEndpoint string `json:"token_endpoint"`
|
||||||
|
JwksUri string `json:"jwks_uri"`
|
||||||
|
ResponseTypesSupported []string `json:"response_types_supported"`
|
||||||
|
SubjectTypesSupported []string `json:"subject_types_supported"`
|
||||||
|
IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported"`
|
||||||
|
ClaimsSupported []string `json:"claims_supported"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SSOHandler) HandleDiscoveryRequest(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
//Prepend https:// if not present
|
||||||
|
authBaseURL := h.Config.AuthURL
|
||||||
|
if !strings.HasPrefix(authBaseURL, "http://") && !strings.HasPrefix(authBaseURL, "https://") {
|
||||||
|
authBaseURL = "https://" + authBaseURL
|
||||||
|
}
|
||||||
|
|
||||||
|
//Handle the discovery request
|
||||||
|
discovery := OpenIDConfiguration{
|
||||||
|
Issuer: authBaseURL,
|
||||||
|
AuthorizationEndpoint: authBaseURL + "/oauth2/authorize",
|
||||||
|
TokenEndpoint: authBaseURL + "/oauth2/token",
|
||||||
|
JwksUri: authBaseURL + "/jwks.json",
|
||||||
|
ResponseTypesSupported: []string{"code", "token"},
|
||||||
|
SubjectTypesSupported: []string{"public"},
|
||||||
|
IDTokenSigningAlgValuesSupported: []string{
|
||||||
|
"RS256",
|
||||||
|
},
|
||||||
|
ClaimsSupported: []string{
|
||||||
|
"sub", //Subject, usually the user ID
|
||||||
|
"iss", //Issuer, usually the server URL
|
||||||
|
"aud", //Audience, usually the client ID
|
||||||
|
"exp", //Expiration Time
|
||||||
|
"iat", //Issued At
|
||||||
|
"email", //Email
|
||||||
|
"locale", //Locale
|
||||||
|
"name", //Full Name
|
||||||
|
"nickname", //Nickname
|
||||||
|
"preferred_username", //Preferred Username
|
||||||
|
"website", //Website
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
//Write the response
|
||||||
|
js, _ := json.Marshal(discovery)
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write(js)
|
||||||
|
}
|
@ -6,6 +6,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-oauth2/oauth2/v4/errors"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -30,21 +31,26 @@ func (h *SSOHandler) InitSSOPortal(portalServerPort int) {
|
|||||||
//Register API endpoint for the SSO portal
|
//Register API endpoint for the SSO portal
|
||||||
pmux.HandleFunc("/sso/login", h.HandleLogin)
|
pmux.HandleFunc("/sso/login", h.HandleLogin)
|
||||||
|
|
||||||
|
//Register API endpoint for autodiscovery
|
||||||
|
pmux.HandleFunc("/.well-known/openid-configuration", h.HandleDiscoveryRequest)
|
||||||
|
|
||||||
//Register OAuth2 endpoints
|
//Register OAuth2 endpoints
|
||||||
h.Oauth2Server.RegisterOauthEndpoints(pmux)
|
h.Oauth2Server.RegisterOauthEndpoints(pmux)
|
||||||
|
|
||||||
h.ssoPortalMux = pmux
|
h.ssoPortalMux = pmux
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartSSOPortal start the SSO portal server
|
// StartSSOPortal start the SSO portal server
|
||||||
// This function will block the main thread, call it in a goroutine
|
// This function will block the main thread, call it in a goroutine
|
||||||
func (h *SSOHandler) StartSSOPortal() error {
|
func (h *SSOHandler) StartSSOPortal() error {
|
||||||
|
if h.ssoPortalServer != nil {
|
||||||
|
return errors.New("SSO portal server already running")
|
||||||
|
}
|
||||||
h.ssoPortalServer = &http.Server{
|
h.ssoPortalServer = &http.Server{
|
||||||
Addr: ":" + strconv.Itoa(h.Config.PortalServerPort),
|
Addr: ":" + strconv.Itoa(h.Config.PortalServerPort),
|
||||||
Handler: h.ssoPortalMux,
|
Handler: h.ssoPortalMux,
|
||||||
}
|
}
|
||||||
err := h.ssoPortalServer.ListenAndServe()
|
err := h.ssoPortalServer.ListenAndServe()
|
||||||
if err != nil {
|
if err != nil && err != http.ErrServerClosed {
|
||||||
h.Log("Failed to start SSO portal server", err)
|
h.Log("Failed to start SSO portal server", err)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
@ -59,14 +65,17 @@ func (h *SSOHandler) StopSSOPortal() error {
|
|||||||
h.Log("Failed to stop SSO portal server", err)
|
h.Log("Failed to stop SSO portal server", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
h.ssoPortalServer = nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartSSOPortal start the SSO portal server
|
// StartSSOPortal start the SSO portal server
|
||||||
func (h *SSOHandler) RestartSSOServer() error {
|
func (h *SSOHandler) RestartSSOServer() error {
|
||||||
err := h.StopSSOPortal()
|
if h.ssoPortalServer != nil {
|
||||||
if err != nil {
|
err := h.StopSSOPortal()
|
||||||
return err
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
go h.StartSSOPortal()
|
go h.StartSSOPortal()
|
||||||
return nil
|
return nil
|
||||||
|
@ -23,13 +23,27 @@ type SubdomainAccessRule struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UserEntry struct {
|
type UserEntry struct {
|
||||||
UserID string //User ID, in UUIDv4 format
|
UserID string `json:sub` //User ID
|
||||||
Username string //Username
|
Username string `json:"name"` //Username
|
||||||
PasswordHash string //Password hash
|
Email string `json:"email"` //Email
|
||||||
TOTPCode string //2FA TOTP code
|
PasswordHash string `json:"passwordhash"` //Password hash
|
||||||
Enable2FA bool //Enable 2FA for this user
|
TOTPCode string `json:"totpcode"` //TOTP code
|
||||||
Subdomains map[string]*SubdomainAccessRule //Subdomain and access rule
|
Enable2FA bool `json:"enable2fa"` //Enable 2FA
|
||||||
parent *SSOHandler //Parent SSO handler
|
Subdomains map[string]*SubdomainAccessRule `json:"subdomains"` //Subdomain access rules
|
||||||
|
LastLogin int64 `json:"lastlogin"` //Last login time
|
||||||
|
LastLoginIP string `json:"lastloginip"` //Last login IP
|
||||||
|
LastLoginCountry string `json:"lastlogincountry"` //Last login country
|
||||||
|
parent *SSOHandler //Parent SSO handler
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientResponse struct {
|
||||||
|
Sub string `json:"sub"` //User ID
|
||||||
|
Name string `json:"name"` //Username
|
||||||
|
Nickname string `json:"nickname"` //Nickname
|
||||||
|
PreferredUsername string `json:"preferred_username"` //Preferred Username
|
||||||
|
Email string `json:"email"` //Email
|
||||||
|
Locale string `json:"locale"` //Locale
|
||||||
|
Website string `json:"website"` //Website
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SSOHandler) SSOUserExists(userid string) bool {
|
func (s *SSOHandler) SSOUserExists(userid string) bool {
|
||||||
@ -113,3 +127,15 @@ func (u *UserEntry) VerifyTotp(enteredCode string) bool {
|
|||||||
totp := gotp.NewDefaultTOTP(u.TOTPCode)
|
totp := gotp.NewDefaultTOTP(u.TOTPCode)
|
||||||
return totp.Verify(enteredCode, time.Now().Unix())
|
return totp.Verify(enteredCode, time.Now().Unix())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *UserEntry) GetClientResponse() ClientResponse {
|
||||||
|
return ClientResponse{
|
||||||
|
Sub: u.UserID,
|
||||||
|
Name: u.Username,
|
||||||
|
Nickname: u.Username,
|
||||||
|
PreferredUsername: u.Username,
|
||||||
|
Email: u.Email,
|
||||||
|
Locale: "en",
|
||||||
|
Website: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
67
src/mod/geodb/locale.go
Normal file
67
src/mod/geodb/locale.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package geodb
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// GetRequesterCountryISOCode get the locale of the requester
|
||||||
|
func (s *Store) GetLocaleFromRequest(r *http.Request) (string, error) {
|
||||||
|
cc := s.GetRequesterCountryISOCode(r)
|
||||||
|
return GetLocaleFromCountryCode(cc), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLocaleFromCountryCode get the locale given the country code
|
||||||
|
func GetLocaleFromCountryCode(cc string) string {
|
||||||
|
//If you find your country is not in the list, please add it here
|
||||||
|
mapCountryToLocale := map[string]string{
|
||||||
|
"aa": "ar_AA",
|
||||||
|
"by": "be_BY",
|
||||||
|
"bg": "bg_BG",
|
||||||
|
"es": "ca_ES",
|
||||||
|
"cz": "cs_CZ",
|
||||||
|
"dk": "da_DK",
|
||||||
|
"ch": "de_CH",
|
||||||
|
"de": "de_DE",
|
||||||
|
"gr": "el_GR",
|
||||||
|
"au": "en_AU",
|
||||||
|
"be": "en_BE",
|
||||||
|
"gb": "en_GB",
|
||||||
|
"jp": "en_JP",
|
||||||
|
"us": "en_US",
|
||||||
|
"za": "en_ZA",
|
||||||
|
"fi": "fi_FI",
|
||||||
|
"ca": "fr_CA",
|
||||||
|
"fr": "fr_FR",
|
||||||
|
"hr": "hr_HR",
|
||||||
|
"hu": "hu_HU",
|
||||||
|
"is": "is_IS",
|
||||||
|
"it": "it_IT",
|
||||||
|
"il": "iw_IL",
|
||||||
|
"kr": "ko_KR",
|
||||||
|
"lt": "lt_LT",
|
||||||
|
"lv": "lv_LV",
|
||||||
|
"mk": "mk_MK",
|
||||||
|
"nl": "nl_NL",
|
||||||
|
"no": "no_NO",
|
||||||
|
"pl": "pl_PL",
|
||||||
|
"br": "pt_BR",
|
||||||
|
"pt": "pt_PT",
|
||||||
|
"ro": "ro_RO",
|
||||||
|
"ru": "ru_RU",
|
||||||
|
"sp": "sh_SP",
|
||||||
|
"sk": "sk_SK",
|
||||||
|
"sl": "sl_SL",
|
||||||
|
"al": "sq_AL",
|
||||||
|
"se": "sv_SE",
|
||||||
|
"th": "th_TH",
|
||||||
|
"tr": "tr_TR",
|
||||||
|
"ua": "uk_UA",
|
||||||
|
"cn": "zh_CN",
|
||||||
|
"tw": "zh_TW",
|
||||||
|
"hk": "zh_HK",
|
||||||
|
}
|
||||||
|
locale, ok := mapCountryToLocale[cc]
|
||||||
|
if !ok {
|
||||||
|
return "en-US"
|
||||||
|
}
|
||||||
|
|
||||||
|
return locale
|
||||||
|
}
|
@ -129,7 +129,6 @@ func startupSequence() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Create an SSO handler
|
//Create an SSO handler
|
||||||
sysdb.NewTable("sso_conf")
|
|
||||||
ssoHandler, err = sso.NewSSOHandler(&sso.SSOConfig{
|
ssoHandler, err = sso.NewSSOHandler(&sso.SSOConfig{
|
||||||
SystemUUID: nodeUUID,
|
SystemUUID: nodeUUID,
|
||||||
PortalServerPort: 5488,
|
PortalServerPort: 5488,
|
||||||
|
@ -8,12 +8,12 @@
|
|||||||
<i class="circle check icon"></i>
|
<i class="circle check icon"></i>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<span class="webserv_status">Running</span>
|
<span class="webserv_status">Running</span>
|
||||||
<div class="sub header">Listen port :<span class="webserv_port">8081</span></div>
|
<div class="sub header">Listen port :<span class="oauthserv_port">8081</span></div>
|
||||||
</div>
|
</div>
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui form">
|
<div class="ui form">
|
||||||
<h4 class="ui dividing header">Oauth2 Server</h4>
|
<h3 class="ui dividing header">Oauth2 Server Settings</h3>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui toggle checkbox">
|
<div class="ui toggle checkbox">
|
||||||
<input type="checkbox" name="enableOauth2">
|
<input type="checkbox" name="enableOauth2">
|
||||||
@ -29,6 +29,35 @@
|
|||||||
</div>
|
</div>
|
||||||
<small>Listening port of the Zoraxy internal Oauth2 Server.You can create a subdomain proxy rule to <code>127.0.0.1:<span class="ssoPort">5488</span></code></small>
|
<small>Listening port of the Zoraxy internal Oauth2 Server.You can create a subdomain proxy rule to <code>127.0.0.1:<span class="ssoPort">5488</span></code></small>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>Auth URL</label>
|
||||||
|
<div class="ui action input">
|
||||||
|
<input type="text" name="authURL" placeholder="https://auth.yourdomain.com">
|
||||||
|
<button id="saveAuthURLBtn" class="ui basic blue button"><i class="ui blue save icon"></i> Save</button>
|
||||||
|
</div>
|
||||||
|
<small>The exposed authentication URL of the Oauth2 server, usually <code>https://auth.example.com</code> or <code>https://sso.yourdomain.com</code>. <b>Remember to include the http:// or https:// in your URL.</b></small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div class="ui form">
|
||||||
|
<h3 class="ui dividing header">Zoraxy SSO Settings</h3>
|
||||||
|
<div class="field">
|
||||||
|
<label>Default Redirection URL </label>
|
||||||
|
<div class="ui fluid input">
|
||||||
|
<input type="text" name="defaultSiteURL" placeholder="https://yourdomain.com">
|
||||||
|
</div>
|
||||||
|
<small>The default URL to redirect to after login if redirection target is not set</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="ui basic button"> <i class="ui green check icon"></i> Apply Changes </button>
|
||||||
|
</div>
|
||||||
|
<div class="ui basic message">
|
||||||
|
<div class="header">
|
||||||
|
<i class="ui yellow exclamation triangle icon"></i> Important Notes about Zoraxy SSO
|
||||||
|
</div>
|
||||||
|
<p>Zoraxy SSO, if enabled in HTTP Proxy rule, will automatically intercept the proxy request and provide an SSO interface on upstreams that do not support OAuth natively.
|
||||||
|
It is basically like basic auth with a login page. <b> The same user credential can be used in OAuth sign-in and Zoraxy SSO sign-in.</b>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
<div>
|
<div>
|
||||||
@ -39,6 +68,26 @@
|
|||||||
<div class="sub header">A list of users that are registered with the SSO server</div>
|
<div class="sub header">A list of users that are registered with the SSO server</div>
|
||||||
</div>
|
</div>
|
||||||
</h3>
|
</h3>
|
||||||
|
<table class="ui celled table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Username</th>
|
||||||
|
<th>Registered On</th>
|
||||||
|
<th>Reset Password</th>
|
||||||
|
<th>Remove</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="registeredSsoUsers">
|
||||||
|
<tr>
|
||||||
|
<td>admin</td>
|
||||||
|
<td>2020-01-01</td>
|
||||||
|
<td><button class="ui blue basic small icon button"><i class="ui blue key icon"></i></button></td>
|
||||||
|
<td><button class="ui red basic small icon button"><i class="ui red trash icon"></i></button></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<button onclick="handleUserListRefresh();" class="ui basic right floated button"><i class="ui green refresh icon"></i> Refresh</button>
|
||||||
|
<button onclick="openRegisteredUserManager();" class="ui basic button"><i class="ui blue users icon"></i> Manage Registered Users</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
<div>
|
<div>
|
||||||
@ -49,6 +98,28 @@
|
|||||||
<div class="sub header">A list of apps that are registered with the SSO server</div>
|
<div class="sub header">A list of apps that are registered with the SSO server</div>
|
||||||
</div>
|
</div>
|
||||||
</h3>
|
</h3>
|
||||||
|
<table class="ui celled table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>App Name</th>
|
||||||
|
<th>Domain</th>
|
||||||
|
<th>App ID</th>
|
||||||
|
<th>Registered On</th>
|
||||||
|
<th>Remove</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="registeredSsoApps">
|
||||||
|
<tr>
|
||||||
|
<td>My App</td>
|
||||||
|
<td><a href="//example.com" target="_blank">example.com</a></td>
|
||||||
|
<td>123456</td>
|
||||||
|
<td>2020-01-01</td>
|
||||||
|
<td><button class="ui red basic small icon button"><i class="ui red trash icon"></i></button></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<button onclick="handleRegisterAppListRefresh();" class="ui basic right floated button"><i class="ui green refresh icon"></i> Refresh</button>
|
||||||
|
<button onclick="openRegisterAppManagementSnippet();" class="ui basic button"><i style="font-size: 1em; margin-top: -0.2em;" class="ui green th large icon"></i> Manage Registered App</button>
|
||||||
<p></p>
|
<p></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -59,7 +130,7 @@
|
|||||||
$(".ssoPort").text($(this).val());
|
$(".ssoPort").text($(this).val());
|
||||||
});
|
});
|
||||||
|
|
||||||
function initSSOStatus(){
|
function updateSSOStatus(){
|
||||||
$.get("/api/sso/status", function(data){
|
$.get("/api/sso/status", function(data){
|
||||||
if(data.error != undefined){
|
if(data.error != undefined){
|
||||||
//Show error message
|
//Show error message
|
||||||
@ -70,35 +141,229 @@
|
|||||||
$(".ssoRunningState").addClass("enabled");
|
$(".ssoRunningState").addClass("enabled");
|
||||||
$("#ssoRunningState .webserv_status").html('Running');
|
$("#ssoRunningState .webserv_status").html('Running');
|
||||||
$(".ssoRunningState i").attr("class", "circle check icon");
|
$(".ssoRunningState i").attr("class", "circle check icon");
|
||||||
|
$("input[name=enableOauth2]").parent().checkbox("set checked");
|
||||||
}else{
|
}else{
|
||||||
$(".ssoRunningState").removeClass("enabled");
|
$(".ssoRunningState").removeClass("enabled");
|
||||||
$("#ssoRunningState .webserv_status").html('Stopped');
|
$("#ssoRunningState .webserv_status").html('Stopped');
|
||||||
$(".ssoRunningState i").attr("class", "circle times icon");
|
$(".ssoRunningState i").attr("class", "circle times icon");
|
||||||
|
$("input[name=enableOauth2]").parent().checkbox("set unchecked");
|
||||||
}
|
}
|
||||||
$("input[name=enableZoraxySSO]").prop("checked", data.Enabled);
|
|
||||||
$("input[name=oauth2Port]").val(data.ListeningPort);
|
$("input[name=oauth2Port]").val(data.ListeningPort);
|
||||||
|
$(".oauthserv_port").text(data.ListeningPort);
|
||||||
|
$("input[name=authURL]").val(data.AuthURL);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function initSSOStatus(){
|
||||||
|
$.get("/api/sso/status", function(data){
|
||||||
|
//Update the SSO status from the server
|
||||||
|
updateSSOStatus();
|
||||||
|
|
||||||
|
//Bind events to the enable checkbox
|
||||||
|
$("input[name=enableOauth2]").off("change").on("change", function(){
|
||||||
|
var checked = $(this).prop("checked");
|
||||||
|
$.cjax({
|
||||||
|
url: "/api/sso/enable",
|
||||||
|
method: "POST",
|
||||||
|
data: {
|
||||||
|
enable: checked
|
||||||
|
},
|
||||||
|
success: function(data){
|
||||||
|
if(data.error != undefined){
|
||||||
|
msgbox("Failed to toggle SSO: " + data.error, false);
|
||||||
|
//Unbind the event to prevent infinite loop
|
||||||
|
$("input[name=enableOauth2]").off("change");
|
||||||
|
}else{
|
||||||
|
initSSOStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
initSSOStatus();
|
initSSOStatus();
|
||||||
|
|
||||||
$("#saveOauthServerPortBtn").on("click", function() {
|
/* Save the Oauth server port */
|
||||||
|
function saveOauthServerPort(){
|
||||||
var port = $("input[name=oauth2Port]").val();
|
var port = $("input[name=oauth2Port]").val();
|
||||||
|
//Check if the port is valid
|
||||||
|
if (port < 1 || port > 65535){
|
||||||
|
msgbox("Invalid port number", false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
//Use cjax to send the port to the server with csrf token
|
//Use cjax to send the port to the server with csrf token
|
||||||
$.cjax({
|
$.cjax({
|
||||||
url: "/api/sso/oauth2/setPort",
|
url: "/api/sso/setPort",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
data: {
|
data: {
|
||||||
port: port
|
port: port
|
||||||
},
|
},
|
||||||
success: function(data) {
|
success: function(data) {
|
||||||
if (data.error != undefined) {
|
if (data.error != undefined) {
|
||||||
msgbox("Oauth server port updated", true);
|
msgbox("Failed to update Oauth server port: " + data.error, false);
|
||||||
} else {
|
} else {
|
||||||
msgbox("Failed to update Oauth server port", false);
|
msgbox("Oauth server port updated", true);
|
||||||
|
|
||||||
|
}
|
||||||
|
updateSSOStatus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//Bind the save button to the saveOauthServerPort function
|
||||||
|
$("#saveOauthServerPortBtn").on("click", function() {
|
||||||
|
saveOauthServerPort();
|
||||||
|
});
|
||||||
|
$("input[name=oauth2Port]").on("keypress", function(e) {
|
||||||
|
if (e.which == 13) {
|
||||||
|
saveOauthServerPort();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Save the Oauth server URL (aka AuthURL) */
|
||||||
|
function saveAuthURL(){
|
||||||
|
var url = $("input[name=authURL]").val();
|
||||||
|
//Make sure the url contains http:// or https://
|
||||||
|
if (!url.startsWith("http://") && !url.startsWith("https://")){
|
||||||
|
msgbox("Invalid URL. Make sure to include http:// or https://", false);
|
||||||
|
$("input[name=authURL]").parent().parent().addClass("error");
|
||||||
|
return;
|
||||||
|
}else{
|
||||||
|
$("input[name=authURL]").parent().parent().removeClass("error");
|
||||||
|
}
|
||||||
|
//Use cjax to send the port to the server with csrf token
|
||||||
|
$.cjax({
|
||||||
|
url: "/api/sso/setAuthURL",
|
||||||
|
method: "POST",
|
||||||
|
data: {
|
||||||
|
"auth_url": url
|
||||||
|
},
|
||||||
|
success: function(data) {
|
||||||
|
if (data.error != undefined) {
|
||||||
|
msgbox("Failed to update Oauth server port: " + data.error, false);
|
||||||
|
} else {
|
||||||
|
msgbox("Oauth server port updated", true);
|
||||||
|
|
||||||
|
}
|
||||||
|
updateSSOStatus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//Bind the save button to the saveAuthURL function
|
||||||
|
$("#saveAuthURLBtn").on("click", function() {
|
||||||
|
saveAuthURL();
|
||||||
|
});
|
||||||
|
$("input[name=authURL]").on("keypress", function(e) {
|
||||||
|
if (e.which == 13) {
|
||||||
|
saveAuthURL();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Registered Apps Event Handlers */
|
||||||
|
|
||||||
|
//Function to initialize the registered app table
|
||||||
|
function initRegisteredAppTable(){
|
||||||
|
$.get("/api/sso/app/list", function(data){
|
||||||
|
if(data.error != undefined){
|
||||||
|
msgbox("Failed to get registered apps: " + data.error, false);
|
||||||
|
}else{
|
||||||
|
var tbody = $("#registeredSsoApps");
|
||||||
|
tbody.empty();
|
||||||
|
for(var i = 0; i < data.length; i++){
|
||||||
|
var app = data[i];
|
||||||
|
var tr = $("<tr>");
|
||||||
|
tr.append($("<td>").text(app.AppName));
|
||||||
|
tr.append($("<td>").html('<a href="//'+app.Domain+'" target="_blank">'+app.Domain+'</a>'));
|
||||||
|
tr.append($("<td>").text(app.AppID));
|
||||||
|
tr.append($("<td>").text(app.RegisteredOn));
|
||||||
|
var removeBtn = $("<button>").addClass("ui red basic small icon button").html('<i class="ui red trash icon"></i>');
|
||||||
|
removeBtn.on("click", function(){
|
||||||
|
removeApp(app.AppID);
|
||||||
|
});
|
||||||
|
tr.append($("<td>").append(removeBtn));
|
||||||
|
tbody.append(tr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.length == 0){
|
||||||
|
tbody.append($("<tr>").append($("<td>").attr("colspan", 4).html(`<i class="ui green circle check icon"></i> No registered users`)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
initRegisteredAppTable();
|
||||||
|
|
||||||
|
//Also bind the refresh button to the initRegisteredAppTable function
|
||||||
|
function handleRegisterAppListRefresh(){
|
||||||
|
initRegisteredAppTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
function openRegisterAppManagementSnippet(){
|
||||||
|
//Open the register app management snippet
|
||||||
|
showSideWrapper("snippet/sso_app.html");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Bind the remove button to the removeApp function
|
||||||
|
function removeApp(appID){
|
||||||
|
$.cjax({
|
||||||
|
url: "/api/sso/removeApp",
|
||||||
|
method: "POST",
|
||||||
|
data: {
|
||||||
|
appID: appID
|
||||||
|
},
|
||||||
|
success: function(data){
|
||||||
|
if(data.error != undefined){
|
||||||
|
msgbox("Failed to remove app: " + data.error, false);
|
||||||
|
}else{
|
||||||
|
msgbox("App removed", true);
|
||||||
|
updateSSOStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Registered Users Event Handlers */
|
||||||
|
function initUserList(){
|
||||||
|
$.get("/api/sso/user/list", function(data){
|
||||||
|
if(data.error != undefined){
|
||||||
|
msgbox("Failed to get registered users: " + data.error, false);
|
||||||
|
}else{
|
||||||
|
var tbody = $("#registeredSsoUsers");
|
||||||
|
tbody.empty();
|
||||||
|
for(var i = 0; i < data.length; i++){
|
||||||
|
var user = data[i];
|
||||||
|
var tr = $("<tr>");
|
||||||
|
tr.append($("<td>").text(user.Username));
|
||||||
|
tr.append($("<td>").text(user.RegisteredOn));
|
||||||
|
var resetBtn = $("<button>").addClass("ui blue basic small icon button").html('<i class="ui blue key icon"></i>');
|
||||||
|
resetBtn.on("click", function(){
|
||||||
|
resetPassword(user.Username);
|
||||||
|
});
|
||||||
|
tr.append($("<td>").append(resetBtn));
|
||||||
|
var removeBtn = $("<button>").addClass("ui red basic small icon button").html('<i class="ui red trash icon"></i>');
|
||||||
|
removeBtn.on("click", function(){
|
||||||
|
removeUser(user.Username);
|
||||||
|
});
|
||||||
|
tr.append($("<td>").append(removeBtn));
|
||||||
|
tbody.append(tr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.length == 0){
|
||||||
|
tbody.append($("<tr>").append($("<td>").attr("colspan", 4).html(`<i class="ui green circle check icon"></i> No registered users`)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//Bind the refresh button to the initUserList function
|
||||||
|
function handleUserListRefresh(){
|
||||||
|
initUserList();
|
||||||
|
}
|
||||||
|
|
||||||
|
function openRegisteredUserManager(){
|
||||||
|
//Open the registered user management snippet
|
||||||
|
showSideWrapper("snippet/sso_user.html");
|
||||||
|
}
|
||||||
</script>
|
</script>
|
27
src/web/snippet/sso_app.html
Normal file
27
src/web/snippet/sso_app.html
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="zoraxy.csrf.Token" content="{{.csrfToken}}">
|
||||||
|
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||||
|
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="../script/semantic/semantic.min.js"></script>
|
||||||
|
<script src="../script/utils.js"></script>
|
||||||
|
<style>
|
||||||
|
body{
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<br>
|
||||||
|
<div class="ui container">
|
||||||
|
<div class="ui basic segment">
|
||||||
|
<h2 class="ui header">SSO App Management</h2>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
<h3>Work in progress</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
27
src/web/snippet/sso_user.html
Normal file
27
src/web/snippet/sso_user.html
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="zoraxy.csrf.Token" content="{{.csrfToken}}">
|
||||||
|
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||||
|
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="../script/semantic/semantic.min.js"></script>
|
||||||
|
<script src="../script/utils.js"></script>
|
||||||
|
<style>
|
||||||
|
body{
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<br>
|
||||||
|
<div class="ui container">
|
||||||
|
<div class="ui basic segment">
|
||||||
|
<h2 class="ui header">SSO User Management</h2>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
<h3>Work in progress</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
x
Reference in New Issue
Block a user