mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-03 06:07:20 +02:00
Merge pull request #568 from JokerQyou/feature/authentik-forward-auth
[WIP] Add Authentik forward auth support
This commit is contained in:
commit
d4c1225f75
@ -83,6 +83,7 @@ func RegisterTLSAPIs(authRouter *auth.RouterDef) {
|
|||||||
// Register the APIs for Authentication handlers like Authelia and OAUTH2
|
// Register the APIs for Authentication handlers like Authelia and OAUTH2
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register the APIs for redirection rules management functions
|
// Register the APIs for redirection rules management functions
|
||||||
|
@ -10,6 +10,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
"flag"
|
"flag"
|
||||||
|
"imuslab.com/zoraxy/mod/auth/sso/authentik"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -142,7 +143,8 @@ var (
|
|||||||
pluginManager *plugins.Manager //Plugin manager for managing plugins
|
pluginManager *plugins.Manager //Plugin manager for managing plugins
|
||||||
|
|
||||||
//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
|
||||||
|
|
||||||
//Helper modules
|
//Helper modules
|
||||||
EmailSender *email.Sender //Email sender that handle email sending
|
EmailSender *email.Sender //Email sender that handle email sending
|
||||||
|
169
src/mod/auth/sso/authentik/authentik.go
Normal file
169
src/mod/auth/sso/authentik/authentik.go
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
package authentik
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/database"
|
||||||
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AuthentikRouterOptions struct {
|
||||||
|
UseHTTPS bool //If the Authentik server is using HTTPS
|
||||||
|
AuthentikURL string //The URL of the Authentik server
|
||||||
|
Logger *logger.Logger
|
||||||
|
Database *database.Database
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthentikRouter struct {
|
||||||
|
options *AuthentikRouterOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAuthentikRouter creates a new AuthentikRouter object
|
||||||
|
func NewAuthentikRouter(options *AuthentikRouterOptions) *AuthentikRouter {
|
||||||
|
options.Database.NewTable("authentik")
|
||||||
|
|
||||||
|
//Read settings from database, if exists
|
||||||
|
options.Database.Read("authentik", "authentikURL", &options.AuthentikURL)
|
||||||
|
options.Database.Read("authentik", "useHTTPS", &options.UseHTTPS)
|
||||||
|
|
||||||
|
return &AuthentikRouter{
|
||||||
|
options: options,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleSetAuthentikURLAndHTTPS is the internal handler for setting the Authentik URL and HTTPS
|
||||||
|
func (ar *AuthentikRouter) HandleSetAuthentikURLAndHTTPS(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method == http.MethodGet {
|
||||||
|
//Return the current settings
|
||||||
|
js, _ := json.Marshal(map[string]interface{}{
|
||||||
|
"useHTTPS": ar.options.UseHTTPS,
|
||||||
|
"authentikURL": ar.options.AuthentikURL,
|
||||||
|
})
|
||||||
|
|
||||||
|
utils.SendJSONResponse(w, string(js))
|
||||||
|
return
|
||||||
|
} else if r.Method == http.MethodPost {
|
||||||
|
//Update the settings
|
||||||
|
AuthentikURL, err := utils.PostPara(r, "authentikURL")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "authentikURL not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
useHTTPS, err := utils.PostBool(r, "useHTTPS")
|
||||||
|
if err != nil {
|
||||||
|
useHTTPS = false
|
||||||
|
}
|
||||||
|
|
||||||
|
//Write changes to runtime
|
||||||
|
ar.options.AuthentikURL = AuthentikURL
|
||||||
|
ar.options.UseHTTPS = useHTTPS
|
||||||
|
|
||||||
|
//Write changes to database
|
||||||
|
ar.options.Database.Write("authentik", "authentikURL", AuthentikURL)
|
||||||
|
ar.options.Database.Write("authentik", "useHTTPS", useHTTPS)
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
} else {
|
||||||
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleAuthentikAuth is the internal handler for Authentik authentication
|
||||||
|
// Set useHTTPS to true if your Authentik server is using HTTPS
|
||||||
|
// Set AuthentikURL to the URL of the Authentik server, e.g. Authentik.example.com
|
||||||
|
func (ar *AuthentikRouter) HandleAuthentikAuth(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
const outpostPrefix = "outpost.goauthentik.io"
|
||||||
|
client := &http.Client{}
|
||||||
|
|
||||||
|
if ar.options.AuthentikURL == "" {
|
||||||
|
ar.options.Logger.PrintAndLog("Authentik", "Authentik URL not set", nil)
|
||||||
|
w.WriteHeader(500)
|
||||||
|
w.Write([]byte("500 - Internal Server Error"))
|
||||||
|
return errors.New("authentik URL not set")
|
||||||
|
}
|
||||||
|
protocol := "http"
|
||||||
|
if ar.options.UseHTTPS {
|
||||||
|
protocol = "https"
|
||||||
|
}
|
||||||
|
|
||||||
|
authentikBaseURL := protocol + "://" + ar.options.AuthentikURL
|
||||||
|
//Remove tailing slash if any
|
||||||
|
authentikBaseURL = strings.TrimSuffix(authentikBaseURL, "/")
|
||||||
|
|
||||||
|
scheme := "http"
|
||||||
|
if r.TLS != nil {
|
||||||
|
scheme = "https"
|
||||||
|
}
|
||||||
|
reqUrl := scheme + "://" + r.Host + r.RequestURI
|
||||||
|
// Pass request to outpost if path matches outpost prefix
|
||||||
|
if reqPath := strings.TrimPrefix(r.URL.Path, "/"); strings.HasPrefix(reqPath, outpostPrefix) {
|
||||||
|
req, err := http.NewRequest(r.Method, authentikBaseURL+r.RequestURI, r.Body)
|
||||||
|
if err != nil {
|
||||||
|
ar.options.Logger.PrintAndLog("Authentik", "Unable to create request", err)
|
||||||
|
w.WriteHeader(401)
|
||||||
|
return errors.New("unauthorized")
|
||||||
|
}
|
||||||
|
req.Header.Set("X-Original-URL", reqUrl)
|
||||||
|
req.Header.Set("Host", r.Host)
|
||||||
|
for _, cookie := range r.Cookies() {
|
||||||
|
req.AddCookie(cookie)
|
||||||
|
}
|
||||||
|
if resp, err := client.Do(req); err != nil {
|
||||||
|
ar.options.Logger.PrintAndLog("Authentik", "Unable to pass request to Authentik outpost", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return errors.New("internal server error")
|
||||||
|
} else {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
for k := range resp.Header {
|
||||||
|
w.Header().Set(k, resp.Header.Get(k))
|
||||||
|
}
|
||||||
|
w.WriteHeader(resp.StatusCode)
|
||||||
|
if _, err = io.Copy(w, resp.Body); err != nil {
|
||||||
|
ar.options.Logger.PrintAndLog("Authentik", "Unable to pass Authentik outpost response to client", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return errors.New("internal server error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//Make a request to Authentik to verify the request
|
||||||
|
req, err := http.NewRequest(http.MethodGet, authentikBaseURL+"/"+outpostPrefix+"/auth/nginx", nil)
|
||||||
|
if err != nil {
|
||||||
|
ar.options.Logger.PrintAndLog("Authentik", "Unable to create request", err)
|
||||||
|
w.WriteHeader(401)
|
||||||
|
return errors.New("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("X-Original-URL", reqUrl)
|
||||||
|
|
||||||
|
// Copy cookies from the incoming request
|
||||||
|
for _, cookie := range r.Cookies() {
|
||||||
|
req.AddCookie(cookie)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Making the verification request
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
ar.options.Logger.PrintAndLog("Authentik", "Unable to verify", err)
|
||||||
|
w.WriteHeader(401)
|
||||||
|
return errors.New("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
redirectURL := authentikBaseURL + "/" + outpostPrefix + "/start?rd=" + url.QueryEscape(scheme+"://"+r.Host+r.URL.String())
|
||||||
|
http.Redirect(w, r, redirectURL, http.StatusSeeOther)
|
||||||
|
return errors.New("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -43,6 +43,12 @@ func handleAuthProviderRouting(sep *ProxyEndpoint, w http.ResponseWriter, r *htt
|
|||||||
h.Parent.Option.Logger.LogHTTPRequest(r, "host", 401)
|
h.Parent.Option.Logger.LogHTTPRequest(r, "host", 401)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
} else if sep.AuthenticationProvider.AuthMethod == AuthMethodAuthentik {
|
||||||
|
err := h.handleAuthentikAuth(w, r)
|
||||||
|
if err != nil {
|
||||||
|
h.Parent.Option.Logger.LogHTTPRequest(r, "host", 401)
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//No authentication provider, do not need to handle
|
//No authentication provider, do not need to handle
|
||||||
@ -106,3 +112,7 @@ func handleBasicAuth(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint)
|
|||||||
func (h *ProxyHandler) handleAutheliaAuth(w http.ResponseWriter, r *http.Request) error {
|
func (h *ProxyHandler) handleAutheliaAuth(w http.ResponseWriter, r *http.Request) error {
|
||||||
return h.Parent.Option.AutheliaRouter.HandleAutheliaAuth(w, r)
|
return h.Parent.Option.AutheliaRouter.HandleAutheliaAuth(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *ProxyHandler) handleAuthentikAuth(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
return h.Parent.Option.AuthentikRouter.HandleAuthentikAuth(w, r)
|
||||||
|
}
|
||||||
|
@ -9,6 +9,7 @@ package dynamicproxy
|
|||||||
*/
|
*/
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
|
"imuslab.com/zoraxy/mod/auth/sso/authentik"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
@ -63,7 +64,8 @@ type RouterOption struct {
|
|||||||
PluginManager *plugins.Manager //Plugin manager for handling plugin routing
|
PluginManager *plugins.Manager //Plugin manager for handling plugin routing
|
||||||
|
|
||||||
/* 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
|
||||||
|
|
||||||
/* Utilities */
|
/* Utilities */
|
||||||
Logger *logger.Logger //Logger for reverse proxy requets
|
Logger *logger.Logger //Logger for reverse proxy requets
|
||||||
@ -143,6 +145,7 @@ const (
|
|||||||
AuthMethodBasic //Basic Auth
|
AuthMethodBasic //Basic Auth
|
||||||
AuthMethodAuthelia //Authelia
|
AuthMethodAuthelia //Authelia
|
||||||
AuthMethodOauth2 //Oauth2
|
AuthMethodOauth2 //Oauth2
|
||||||
|
AuthMethodAuthentik
|
||||||
)
|
)
|
||||||
|
|
||||||
type AuthenticationProvider struct {
|
type AuthenticationProvider struct {
|
||||||
|
@ -116,6 +116,7 @@ func ReverseProxtInit() {
|
|||||||
WebDirectory: *path_webserver,
|
WebDirectory: *path_webserver,
|
||||||
AccessController: accessController,
|
AccessController: accessController,
|
||||||
AutheliaRouter: autheliaRouter,
|
AutheliaRouter: autheliaRouter,
|
||||||
|
AuthentikRouter: authentikRouter,
|
||||||
LoadBalancer: loadBalancer,
|
LoadBalancer: loadBalancer,
|
||||||
PluginManager: pluginManager,
|
PluginManager: pluginManager,
|
||||||
/* Utilities */
|
/* Utilities */
|
||||||
@ -587,6 +588,8 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
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 {
|
||||||
|
newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodAuthentik
|
||||||
} 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/authentik"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@ -148,6 +149,13 @@ func startupSequence() {
|
|||||||
Database: sysdb,
|
Database: sysdb,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
authentikRouter = authentik.NewAuthentikRouter(&authentik.AuthentikRouterOptions{
|
||||||
|
UseHTTPS: false, // Automatic populate in router initiation
|
||||||
|
AuthentikURL: "", // Automatic populate in router initiation
|
||||||
|
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,
|
||||||
|
@ -174,6 +174,7 @@
|
|||||||
${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 != 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`:``}
|
||||||
${subd.AuthenticationProvider.AuthMethod == 0x0 && !subd.RequireRateLimit?`<small style="opacity: 0.3; pointer-events: none; user-select: none;">No Special Settings</small>`:""}
|
${subd.AuthenticationProvider.AuthMethod == 0x0 && !subd.RequireRateLimit?`<small style="opacity: 0.3; pointer-events: none; user-select: none;">No Special Settings</small>`:""}
|
||||||
@ -382,6 +383,12 @@
|
|||||||
<label>Authelia</label>
|
<label>Authelia</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<div class="ui radio checkbox">
|
||||||
|
<input type="radio" value="4" name="authProviderType" ${authProvider==0x4?"checked":""}>
|
||||||
|
<label>Authentik</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>
|
||||||
|
@ -34,6 +34,27 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
|
<div class="ui basic segment">
|
||||||
|
<h3>Authentik</h3>
|
||||||
|
<p>Configuration settings for Authentik authentication provider.</p>
|
||||||
|
|
||||||
|
<form class="ui form">
|
||||||
|
<div class="field">
|
||||||
|
<label for="authentikServerUrl">Authentik Server URL</label>
|
||||||
|
<input type="text" id="authentikServerUrl" name="authentikServerUrl" placeholder="Enter Authentik Server URL">
|
||||||
|
<small>Example: auth.example.com</small>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<div class="ui checkbox">
|
||||||
|
<input type="checkbox" id="authentikUseHttps" name="useHttps">
|
||||||
|
<label for="authentikUseHttps">Use HTTPS</label>
|
||||||
|
<small>Check this if your Authentik server uses HTTPS</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="ui basic button" onclick="event.preventDefault(); updateAuthentikSettings();"><i class="green check icon"></i> Apply Change</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="ui divider"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@ -50,6 +71,18 @@
|
|||||||
console.error('Error fetching SSO settings:', textStatus, errorThrown);
|
console.error('Error fetching SSO settings:', textStatus, errorThrown);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
$.cjax({
|
||||||
|
url: '/api/sso/Authentik',
|
||||||
|
method: 'GET',
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(data) {
|
||||||
|
$('#authentikServerUrl').val(data.authentikURL);
|
||||||
|
$('#authentikUseHttps').prop('checked', data.useHTTPS);
|
||||||
|
},
|
||||||
|
error: function(jqXHR, textStatus, errorThrown) {
|
||||||
|
console.error('Error fetching SSO settings:', textStatus, errorThrown);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function updateAutheliaSettings(){
|
function updateAutheliaSettings(){
|
||||||
@ -76,4 +109,28 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
function updateAuthentikSettings(){
|
||||||
|
var authentikServerUrl = $('#authentikServerUrl').val();
|
||||||
|
var useHttps = $('#authentikUseHttps').prop('checked');
|
||||||
|
|
||||||
|
$.cjax({
|
||||||
|
url: '/api/sso/Authentik',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
authentikURL: authentikServerUrl,
|
||||||
|
useHTTPS: useHttps
|
||||||
|
},
|
||||||
|
success: function(data) {
|
||||||
|
if (data.error != undefined) {
|
||||||
|
$.msgbox(data.error, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
msgbox('Authentik settings updated', true);
|
||||||
|
console.log('Authentik settings updated:', data);
|
||||||
|
},
|
||||||
|
error: function(jqXHR, textStatus, errorThrown) {
|
||||||
|
console.error('Error updating Authentik settings:', textStatus, errorThrown);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
Loading…
x
Reference in New Issue
Block a user