From 6d93ac7cb914f03a2beace117569c394429d6c72 Mon Sep 17 00:00:00 2001 From: Victor Date: Fri, 3 Sep 2021 11:25:45 +0100 Subject: [PATCH] fix: propagate 403 and other codes from Authelia --- README.md | 6 +++--- clientHandler.go | 37 +++++++++++++++++--------------- main.go | 56 +++++++++++++++++++++++++++++------------------- util/util.go | 8 +++++++ 4 files changed, 65 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 01987bb..e4eeebe 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,11 @@ You ---> nginx (or other reverse proxy) ---> this reverse proxy --> Authelia This proxy will clone the client's request headers and cookies based on a whitelist, and use them to negotiate authentication with Authelia on the client's behalf. -The proxy will first execute a sub-request to Authelia's `verify` endpoint to check if the client has a valid session cookie or authorization (e.g. basic auth). If that succeeds, code `200` is returned to the client directly. +The proxy will first execute a sub-request to Authelia's `verify` endpoint to check if the client has a valid session cookie or authorization (e.g. basic auth). If that succeeds, code `2xx` is returned to the client directly. -If that fails, the proxy will attempt to detect if the special credentials format is being used. If yes, it will decode the credentials (which include the TOTP) and execute standard Authelia 2FA TOTP authentication. The proxy will then verify the newly obtained session, and, if valid, return the session cookie to the client through a `Set-Cookie` header, along with a status code `200`. +If that fails, the proxy will attempt to detect if the special credentials format is being used. If yes, it will decode the credentials (which include the TOTP) and execute standard Authelia 2FA TOTP authentication. The proxy will then verify the newly obtained session, and, if valid, return the session cookie to the client through a `Set-Cookie` header, along with a status code `2xx`. -In all other cases, including when the client does not use the special credentials format or the format is invalid, this proxy will return a status code `401`. +In all other cases, including when the client does not use the special credentials format or the format is invalid, this proxy will return a `non-2xx` code. ## Format diff --git a/clientHandler.go b/clientHandler.go index 9536793..0fd578d 100644 --- a/clientHandler.go +++ b/clientHandler.go @@ -40,7 +40,7 @@ func NewClientHandler(ctx echo.Context) *ClientHandler { } // Performs first factor authentication with Authelia and returns the JSON response status -func (a *ClientHandler) checkFirstFactor(credentials *Credentials) (bool, error) { +func (a *ClientHandler) checkFirstFactor(credentials *Credentials) (int, error) { return a.doStatusPost(&authelia.FirstFactorRequest{ Username: credentials.Username, Password: credentials.Password, @@ -49,35 +49,38 @@ func (a *ClientHandler) checkFirstFactor(credentials *Credentials) (bool, error) } // Performs TOTP second factor authentication with Authelia and returns the JSON response status -func (a *ClientHandler) checkTOTP(credentials *Credentials) (bool, error) { +func (a *ClientHandler) checkTOTP(credentials *Credentials) (int, error) { return a.doStatusPost(&authelia.TOTPRequest{ Token: credentials.TOTP, }, authelia.TOTPUrl, false) } // Performs a POST request to an Authelia endpoint and returns the JSON response status -func (a *ClientHandler) doStatusPost(data interface{}, endpoint string, includeAuthorization bool) (bool, error) { +func (a *ClientHandler) doStatusPost(data interface{}, endpoint string, includeAuthorization bool) (int, error) { jsonBody, err := json.Marshal(data) if err != nil { - return false, err + return 0, err } - resp, err := a.doRequest(endpoint, "POST", jsonBody, includeAuthorization) - if err != nil || resp.StatusCode != 200 { - return false, err + if err != nil { + return 0, err + } + if util.IsBad(resp.StatusCode) { + return resp.StatusCode, nil } bodyBytes, err := ioutil.ReadAll(resp.Body) if err != nil { - return false, err + return 0, err } statusResponse := authelia.StatusResponse{} if err = json.Unmarshal(bodyBytes, &statusResponse); err != nil { - return false, err + return 0, err } if statusResponse.Status == "OK" { - return true, nil + return resp.StatusCode, nil + } else { + return 500, nil } - return false, nil } // Adds whitelisted headers from the client's original request to a sub-request @@ -164,23 +167,23 @@ func (a *ClientHandler) doRequest( } // Checks if the client has valid Authorization -func (a *ClientHandler) checkAuthorization() (bool, map[string]string, error) { +func (a *ClientHandler) checkAuthorization() (int, map[string]string, error) { resp, err := a.doRequest(authelia.VerifyUrl, "GET", nil, true) if err != nil { - return false, nil, err + return 0, nil, err } returnHeaders := a.getServerReturnHeaders(resp) - return resp.StatusCode == 200, returnHeaders, nil + return resp.StatusCode, returnHeaders, nil } // Checks if the client has a valid Authelia session -func (a *ClientHandler) checkSession() (bool, map[string]string, error) { +func (a *ClientHandler) checkSession() (int, map[string]string, error) { resp, err := a.doRequest(authelia.VerifyUrl, "GET", nil, false) if err != nil { - return false, nil, err + return 0, nil, err } returnHeaders := a.getServerReturnHeaders(resp) - return resp.StatusCode == 200, returnHeaders, nil + return resp.StatusCode, returnHeaders, nil } func (a *ClientHandler) getServerReturnHeaders(resp *http.Response) map[string]string { diff --git a/main.go b/main.go index 1189a8d..51ecb82 100644 --- a/main.go +++ b/main.go @@ -37,24 +37,25 @@ func main() { func handleAuthentication(ctx echo.Context) error { user := fmt.Sprint("User " + ctx.RealIP()) util.SLogger.Debug(user + " connected") - authenticated, returnHeaders, err := checkAuthentication(ctx) + statusCode, returnHeaders, err := checkAuthentication(ctx) if err != nil { util.SLogger.Error(user + " not authenticated") util.SLogger.Error(err) - return ctx.NoContent(401) + return ctx.NoContent(500) } - if authenticated { + if util.IsGood(statusCode) { util.SLogger.Info(user + " authenticated") for key, value := range returnHeaders { ctx.Response().Header().Set(key, value) } - return ctx.NoContent(200) + return ctx.NoContent(statusCode) + } else { + util.SLogger.Info(user + " not authenticated") + return ctx.NoContent(statusCode) } - util.SLogger.Info(user + " not authenticated") - return ctx.NoContent(401) } -func checkAuthentication(ctx echo.Context) (bool, map[string]string, error) { +func checkAuthentication(ctx echo.Context) (int, map[string]string, error) { clientHandler := NewClientHandler(ctx) // apply all proxyCookies to the response, e.g. newly created Authelia session defer func() { @@ -65,39 +66,50 @@ func checkAuthentication(ctx echo.Context) (bool, map[string]string, error) { }() util.SLogger.Debug("Checking if user session is already valid") - sessionValid, returnHeaders, err := clientHandler.checkSession() + statusCode, returnHeaders, err := clientHandler.checkSession() if err != nil { - return false, nil, err + return 0, nil, err } - if sessionValid { + if util.IsGood(statusCode) { util.SLogger.Debug("User session was valid") - return true, returnHeaders, nil + return statusCode, returnHeaders, nil + } else if statusCode != 401 { + return statusCode, nil, nil } util.SLogger.Debug("Checking if user authorization is valid") - authorizationValid, returnHeaders, err := clientHandler.checkAuthorization() + statusCode, returnHeaders, err = clientHandler.checkAuthorization() if err != nil { - return false, nil, err + return 0, nil, err } - if authorizationValid { + if util.IsGood(statusCode) { util.SLogger.Debug("Authorization was valid") - return true, returnHeaders, nil + return statusCode, returnHeaders, nil + } else if statusCode != 401 { + return statusCode, nil, nil } util.SLogger.Debug("Performing manual authentication") credentials, err := DecodeCredentials(ctx) if err != nil { - return false, nil, err + util.SLogger.Debug(err) + return 401, nil, nil } util.SLogger.Debug("Checking first factor authentication") - result, err := clientHandler.checkFirstFactor(credentials) - if err != nil || !result { - return false, nil, err + statusCode, err = clientHandler.checkFirstFactor(credentials) + if err != nil { + return 0, nil, err + } + if util.IsBad(statusCode) { + return statusCode, nil, nil } util.SLogger.Debug("Checking TOTP authentication") - result, err = clientHandler.checkTOTP(credentials) - if err != nil || !result { - return false, nil, err + statusCode, err = clientHandler.checkTOTP(credentials) + if err != nil { + return 0, nil, err + } + if util.IsBad(statusCode) { + return statusCode, nil, nil } util.SLogger.Debug("Checking if new session is valid") diff --git a/util/util.go b/util/util.go index c5e22dc..879475b 100644 --- a/util/util.go +++ b/util/util.go @@ -67,3 +67,11 @@ func init() { HeaderServerWhitelist[strings.ToLower(header)] = true } } + +func IsGood(code int) bool { + return code >= 200 && code < 300 +} + +func IsBad(code int) bool { + return !IsGood(code) +}