zoraxy/src/mod/auth/sso/authentik/authentik.go

170 lines
5.0 KiB
Go

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
}