diff --git a/src/mod/auth/sso/deprecated/authelia/README.txt b/src/mod/auth/sso/deprecated/authelia/README.txt new file mode 100644 index 0000000..24c4e91 --- /dev/null +++ b/src/mod/auth/sso/deprecated/authelia/README.txt @@ -0,0 +1,7 @@ +Module: authelia + +Notice: +This module is **deprecated** and is no longer in use. It has been retained here for reference purposes only. +Consider using the updated implementation or alternative solutions as this module may be removed in future updates. + +Original implementation: https://github.com/tobychui/zoraxy/pull/421 \ No newline at end of file diff --git a/src/mod/auth/sso/deprecated/authelia/authelia.go b/src/mod/auth/sso/deprecated/authelia/authelia.go new file mode 100644 index 0000000..d86f374 --- /dev/null +++ b/src/mod/auth/sso/deprecated/authelia/authelia.go @@ -0,0 +1,164 @@ +package authelia + +import ( + "encoding/json" + "errors" + "net" + "net/http" + "net/url" + "strings" + + "imuslab.com/zoraxy/mod/database" + "imuslab.com/zoraxy/mod/info/logger" + "imuslab.com/zoraxy/mod/utils" +) + +type AutheliaRouterOptions struct { + UseHTTPS bool //If the Authelia server is using HTTPS + AutheliaURL string //The URL of the Authelia server + Logger *logger.Logger + Database *database.Database +} + +type AutheliaRouter struct { + options *AutheliaRouterOptions +} + +// NewAutheliaRouter creates a new AutheliaRouter object +func NewAutheliaRouter(options *AutheliaRouterOptions) *AutheliaRouter { + options.Database.NewTable("authelia") + + //Read settings from database, if exists + options.Database.Read("authelia", "autheliaURL", &options.AutheliaURL) + options.Database.Read("authelia", "useHTTPS", &options.UseHTTPS) + + return &AutheliaRouter{ + options: options, + } +} + +// HandleSetAutheliaURLAndHTTPS is the internal handler for setting the Authelia URL and HTTPS +func (ar *AutheliaRouter) HandleSetAutheliaURLAndHTTPS(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, + "autheliaURL": ar.options.AutheliaURL, + }) + + utils.SendJSONResponse(w, string(js)) + return + } else if r.Method == http.MethodPost { + //Update the settings + autheliaURL, err := utils.PostPara(r, "autheliaURL") + if err != nil { + utils.SendErrorResponse(w, "autheliaURL not found") + return + } + + useHTTPS, err := utils.PostBool(r, "useHTTPS") + if err != nil { + useHTTPS = false + } + + //Write changes to runtime + ar.options.AutheliaURL = autheliaURL + ar.options.UseHTTPS = useHTTPS + + //Write changes to database + ar.options.Database.Write("authelia", "autheliaURL", autheliaURL) + ar.options.Database.Write("authelia", "useHTTPS", useHTTPS) + + utils.SendOK(w) + } else { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + +} + +// handleAutheliaAuth is the internal handler for Authelia authentication +// Set useHTTPS to true if your authelia server is using HTTPS +// Set autheliaURL to the URL of the Authelia server, e.g. authelia.example.com +func (ar *AutheliaRouter) HandleAutheliaAuth(w http.ResponseWriter, r *http.Request) error { + client := &http.Client{} + + if ar.options.AutheliaURL == "" { + ar.options.Logger.PrintAndLog("Authelia", "Authelia URL not set", nil) + w.WriteHeader(500) + w.Write([]byte("500 - Internal Server Error")) + return errors.New("authelia URL not set") + } + protocol := "http" + if ar.options.UseHTTPS { + protocol = "https" + } + + autheliaURL := &url.URL{ + Scheme: protocol, + Host: ar.options.AutheliaURL, + } + + //Make a request to Authelia to verify the request + req, err := http.NewRequest("POST", autheliaURL.JoinPath("api", "verify").String(), nil) + if err != nil { + ar.options.Logger.PrintAndLog("Authelia", "Unable to create request", err) + w.WriteHeader(401) + return errors.New("unauthorized") + } + + originalURL := rOriginalHeaders(r, req) + + // 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("Authelia", "Unable to verify", err) + w.WriteHeader(401) + return errors.New("unauthorized") + } + + if resp.StatusCode != 200 { + redirectURL := autheliaURL.JoinPath() + + query := redirectURL.Query() + + query.Set("rd", originalURL.String()) + query.Set("rm", r.Method) + + http.Redirect(w, r, redirectURL.String(), http.StatusSeeOther) + return errors.New("unauthorized") + } + + return nil +} + +func rOriginalHeaders(r, req *http.Request) *url.URL { + if r.RemoteAddr != "" { + before, _, _ := strings.Cut(r.RemoteAddr, ":") + + if ip := net.ParseIP(before); ip != nil { + req.Header.Set("X-Forwarded-For", ip.String()) + } + } + + originalURL := &url.URL{ + Scheme: "http", + Host: r.Host, + Path: r.URL.Path, + RawPath: r.URL.RawPath, + } + + if r.TLS != nil { + originalURL.Scheme = "https" + } + + req.Header.Add("X-Forwarded-Method", r.Method) + req.Header.Add("X-Original-URL", originalURL.String()) + + return originalURL +} diff --git a/src/mod/auth/sso/deprecated/authentik/README.txt b/src/mod/auth/sso/deprecated/authentik/README.txt new file mode 100644 index 0000000..5a0912d --- /dev/null +++ b/src/mod/auth/sso/deprecated/authentik/README.txt @@ -0,0 +1,7 @@ +Module: authentik + +Notice: +This module is **deprecated** and is no longer in use. It has been retained here for reference purposes only. +Consider using the updated implementation or alternative solutions as this module may be removed in future updates. + +Original implementation: https://github.com/tobychui/zoraxy/pull/568 \ No newline at end of file diff --git a/src/mod/auth/sso/deprecated/authentik/authentik.go b/src/mod/auth/sso/deprecated/authentik/authentik.go new file mode 100644 index 0000000..795b0b3 --- /dev/null +++ b/src/mod/auth/sso/deprecated/authentik/authentik.go @@ -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 +} diff --git a/src/start.go b/src/start.go index 7a1d0b4..bc9d363 100644 --- a/src/start.go +++ b/src/start.go @@ -9,8 +9,6 @@ import ( "strings" "time" - "imuslab.com/zoraxy/mod/auth/sso/authentik" - "github.com/gorilla/csrf" "imuslab.com/zoraxy/mod/access" "imuslab.com/zoraxy/mod/acme"