Skip to content

Commit

Permalink
Do a small refactor on the MFAHandler in preparation for the API role.
Browse files Browse the repository at this point in the history
  • Loading branch information
marcelosalloum committed Jan 13, 2025
1 parent 5c7e362 commit a5a61a6
Show file tree
Hide file tree
Showing 3 changed files with 298 additions and 292 deletions.
8 changes: 8 additions & 0 deletions internal/serve/httphandler/login_handler_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package httphandler

import (
"bytes"
"encoding/json"
"errors"
"io"
"net/http"
Expand Down Expand Up @@ -100,6 +102,12 @@ func Test_LoginHandler_validateRequest(t *testing.T) {
}
}

func requestToJSON(t *testing.T, req interface{}) io.Reader {
body, err := json.Marshal(req)
require.NoError(t, err)
return bytes.NewReader(body)
}

// TODO: tests with reCaptcha enabled and disabled
func Test_LoginHandler_ServeHTTP(t *testing.T) {
r := chi.NewRouter()
Expand Down
50 changes: 30 additions & 20 deletions internal/serve/httphandler/mfa_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,51 +33,61 @@ type MFAHandler struct {

const DeviceIDHeader = "Device-ID"

func (h MFAHandler) validateRequest(req MFARequest, deviceID string) *httperror.HTTPError {
lv := validators.NewValidator()

lv.Check(req.MFACode != "", "mfa_code", "MFA Code is required")
lv.Check(h.ReCAPTCHADisabled || req.ReCAPTCHAToken != "", "recaptcha_token", "reCAPTCHA token is required")

lv.Check(deviceID != "", DeviceIDHeader, DeviceIDHeader+" header is required")

if lv.HasErrors() {
return httperror.BadRequest("", nil, lv.Errors)
}

return nil
}

func (h MFAHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
ctx := req.Context()

// Step 1: Decode and validate the incoming request
var reqBody MFARequest
if err := httpdecode.DecodeJSON(req, &reqBody); err != nil {
log.Ctx(ctx).Errorf("decoding the request body: %s", err.Error())
httperror.BadRequest("", err, nil).Render(rw)
return
}
deviceID := req.Header.Get(DeviceIDHeader)
if httpErr := h.validateRequest(reqBody, deviceID); httpErr != nil {
httpErr.Render(rw)
return
}

// validating reCAPTCHA Token
// Step 2: Run the reCAPTCHA validation if it is enabled
if !h.ReCAPTCHADisabled {
isValid, recaptchaErr := h.ReCAPTCHAValidator.IsTokenValid(ctx, reqBody.ReCAPTCHAToken)
if recaptchaErr != nil {
httperror.InternalError(ctx, "Cannot validate reCAPTCHA token", recaptchaErr, nil).Render(rw)
isValid, err := h.ReCAPTCHAValidator.IsTokenValid(ctx, reqBody.ReCAPTCHAToken)
if err != nil {
httperror.InternalError(ctx, "Cannot validate reCAPTCHA token", err, nil).Render(rw)
return
}

if !isValid {
log.Ctx(ctx).Errorf("reCAPTCHA token is invalid for request with email")
log.Ctx(ctx).Errorf("reCAPTCHA token is invalid for request with device ID %s", deviceID)
httperror.BadRequest("reCAPTCHA token invalid", nil, nil).Render(rw)
return
}
}

if reqBody.MFACode == "" {
extras := map[string]interface{}{"mfa_code": "MFA Code is required"}
httperror.BadRequest("Request invalid", nil, extras).Render(rw)
return
}

deviceID := req.Header.Get(DeviceIDHeader)
if deviceID == "" {
httperror.BadRequest("Device-ID header is required", nil, nil).Render(rw)
return
}

// Step 3: Authenticate the user with the MFA code
token, err := h.AuthManager.AuthenticateMFA(ctx, deviceID, reqBody.MFACode, reqBody.RememberMe)
if err != nil {
if errors.Is(err, auth.ErrMFACodeInvalid) {
httperror.Unauthorized("", err, nil).Render(rw)
return
} else {
log.Ctx(ctx).Errorf("authenticating user: %s", err.Error())
httperror.InternalError(ctx, "Cannot authenticate user", err, nil).Render(rw)
}
log.Ctx(ctx).Errorf("authenticating user: %s", err.Error())
httperror.InternalError(ctx, "Cannot authenticate user", err, nil).Render(rw)
return
}

Expand Down
Loading

0 comments on commit a5a61a6

Please sign in to comment.