Skip to content
This repository has been archived by the owner on Jun 16, 2024. It is now read-only.

Commit

Permalink
fix: propagate 403 and other codes from Authelia
Browse files Browse the repository at this point in the history
  • Loading branch information
ViRb3 committed Sep 5, 2021
1 parent 2ea6d10 commit 6d93ac7
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 42 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
37 changes: 20 additions & 17 deletions clientHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
56 changes: 34 additions & 22 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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")
Expand Down
8 changes: 8 additions & 0 deletions util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

0 comments on commit 6d93ac7

Please sign in to comment.