From 0a4a92aee9d3195cea5ecef0a88a4a64c83cfaa5 Mon Sep 17 00:00:00 2001 From: "joel@joellee.org" Date: Tue, 28 Nov 2023 18:01:07 +0800 Subject: [PATCH 1/5] refactor: split out enroll tests --- internal/api/mfa_test.go | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/internal/api/mfa_test.go b/internal/api/mfa_test.go index e3686c9a46..cba48ab0a3 100644 --- a/internal/api/mfa_test.go +++ b/internal/api/mfa_test.go @@ -80,6 +80,12 @@ func (ts *MFATestSuite) SetupTest() { func (ts *MFATestSuite) TestEnrollFactor() { testFriendlyName := "bob" alternativeFriendlyName := "john" + user, err := models.FindUserByEmailAndAudience(ts.API.db, "test@example.com", ts.Config.JWT.Aud) + ts.Require().NoError(err) + + token, _, err := generateAccessToken(ts.API.db, user, nil, &ts.Config.JWT) + + require.NoError(ts.T(), err) var cases = []struct { desc string friendlyName string @@ -119,20 +125,8 @@ func (ts *MFATestSuite) TestEnrollFactor() { } for _, c := range cases { ts.Run(c.desc, func() { - var buffer bytes.Buffer - require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(map[string]string{"friendly_name": c.friendlyName, "factor_type": c.factorType, "issuer": c.issuer})) - user, err := models.FindUserByEmailAndAudience(ts.API.db, "test@example.com", ts.Config.JWT.Aud) - ts.Require().NoError(err) - token, _, err := generateAccessToken(ts.API.db, user, nil, &ts.Config.JWT) - require.NoError(ts.T(), err) - - w := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodPost, "/factors", &buffer) - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) - req.Header.Set("Content-Type", "application/json") - ts.API.handler.ServeHTTP(w, req) - require.Equal(ts.T(), c.expectedCode, w.Code) + w := enroll(ts, token, c.friendlyName, c.factorType, c.issuer, c.expectedCode) factors, err := models.FindFactorsByUser(ts.API.db, user) ts.Require().NoError(err) @@ -176,6 +170,8 @@ func (ts *MFATestSuite) TestChallengeFactor() { } func (ts *MFATestSuite) TestMFAVerifyFactor() { + user, err := models.FindUserByEmailAndAudience(ts.API.db, ts.TestEmail, ts.Config.JWT.Aud) + ts.Require().NoError(err) cases := []struct { desc string validChallenge bool @@ -204,8 +200,7 @@ func (ts *MFATestSuite) TestMFAVerifyFactor() { for _, v := range cases { ts.Run(v.desc, func() { // Authenticate users and set secret - user, err := models.FindUserByEmailAndAudience(ts.API.db, ts.TestEmail, ts.Config.JWT.Aud) - ts.Require().NoError(err) + var buffer bytes.Buffer r, err := models.GrantAuthenticatedUser(ts.API.db, user, models.GrantParams{}) require.NoError(ts.T(), err) @@ -464,17 +459,23 @@ func signUpAndVerify(ts *MFATestSuite, email, password string) (verifyResp *Acce } -func enrollAndVerify(ts *MFATestSuite, user *models.User, token string) (verifyResp *AccessTokenResponse) { +func enroll(ts *MFATestSuite, token, friendlyName, factorType, issuer string, expectedCode int) *httptest.ResponseRecorder { var buffer bytes.Buffer w := httptest.NewRecorder() - require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(map[string]string{"friendly_name": "john", "factor_type": models.TOTP, "issuer": ts.TestDomain})) + require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(map[string]string{"friendly_name": friendlyName, "factor_type": factorType, "issuer": issuer})) req := httptest.NewRequest(http.MethodPost, "http://localhost/factors/", &buffer) req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) req.Header.Set("Content-Type", "application/json") ts.API.handler.ServeHTTP(w, req) - require.Equal(ts.T(), http.StatusOK, w.Code) + require.Equal(ts.T(), expectedCode, w.Code) + return w + +} + +func enrollAndVerify(ts *MFATestSuite, user *models.User, token string) (verifyResp *AccessTokenResponse) { + w := enroll(ts, token, "", models.TOTP, ts.TestDomain, http.StatusOK) enrollResp := EnrollFactorResponse{} require.NoError(ts.T(), json.NewDecoder(w.Body).Decode(&enrollResp)) factorID := enrollResp.ID @@ -482,7 +483,7 @@ func enrollAndVerify(ts *MFATestSuite, user *models.User, token string) (verifyR // Challenge var challengeBuffer bytes.Buffer x := httptest.NewRecorder() - req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost/factors/%s/challenge", factorID), &challengeBuffer) + req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost/factors/%s/challenge", factorID), &challengeBuffer) req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) req.Header.Set("Content-Type", "application/json") ts.API.handler.ServeHTTP(x, req) From 75affce44eed12bf0be73b52b02574bca57156ae Mon Sep 17 00:00:00 2001 From: "joel@joellee.org" Date: Tue, 28 Nov 2023 18:10:33 +0800 Subject: [PATCH 2/5] refactor: move setup up --- internal/api/mfa_test.go | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/internal/api/mfa_test.go b/internal/api/mfa_test.go index cba48ab0a3..9bd20a6de3 100644 --- a/internal/api/mfa_test.go +++ b/internal/api/mfa_test.go @@ -53,7 +53,7 @@ func (ts *MFATestSuite) SetupTest() { f, err := models.NewFactor(u, "test_factor", models.TOTP, models.FactorStateUnverified, "secretkey") require.NoError(ts.T(), err, "Error creating test factor model") require.NoError(ts.T(), ts.API.db.Create(f), "Error saving new test factor") - // Create corresponding sessoin + // Create corresponding session s, err := models.NewSession() require.NoError(ts.T(), err, "Error creating test session") s.UserID = u.ID @@ -266,7 +266,18 @@ func (ts *MFATestSuite) TestMFAVerifyFactor() { } } +func (ts *MFATestSuite) setupUserAndSession() (*models.User, *models.Session) { + user, err := models.FindUserByEmailAndAudience(ts.API.db, ts.TestEmail, ts.Config.JWT.Aud) + require.NoError(ts.T(), err) + session, err := models.FindSessionByUserID(ts.API.db, user.ID) + require.NoError(ts.T(), err) + return user, session +} + func (ts *MFATestSuite) TestUnenrollVerifiedFactor() { + + user, session := ts.setupUserAndSession() + cases := []struct { desc string isAAL2 bool @@ -284,25 +295,20 @@ func (ts *MFATestSuite) TestUnenrollVerifiedFactor() { }, } for _, v := range cases { - ts.Run(v.desc, func() { // Create User - u, err := models.FindUserByEmailAndAudience(ts.API.db, "test@example.com", ts.Config.JWT.Aud) - require.NoError(ts.T(), err) - s, err := models.FindSessionByUserID(ts.API.db, u.ID) - require.NoError(ts.T(), err) if v.isAAL2 { - s.UpdateAssociatedAAL(ts.API.db, models.AAL2.String()) + session.UpdateAssociatedAAL(ts.API.db, models.AAL2.String()) } var secondarySession *models.Session // Create Session to test behaviour which downgrades other sessions - factors, err := models.FindFactorsByUser(ts.API.db, u) + factors, err := models.FindFactorsByUser(ts.API.db, user) require.NoError(ts.T(), err, "error finding factors") f := factors[0] secondarySession, err = models.NewSession() require.NoError(ts.T(), err, "Error creating test session") - secondarySession.UserID = u.ID + secondarySession.UserID = user.ID secondarySession.FactorID = &f.ID require.NoError(ts.T(), ts.API.db.Create(secondarySession), "Error saving test session") @@ -314,7 +320,7 @@ func (ts *MFATestSuite) TestUnenrollVerifiedFactor() { var buffer bytes.Buffer - token, _, err := generateAccessToken(ts.API.db, u, &s.ID, &ts.Config.JWT) + token, _, err := generateAccessToken(ts.API.db, user, &session.ID, &ts.Config.JWT) require.NoError(ts.T(), err) w := httptest.NewRecorder() @@ -337,17 +343,15 @@ func (ts *MFATestSuite) TestUnenrollVerifiedFactor() { } func (ts *MFATestSuite) TestUnenrollUnverifiedFactor() { - u, err := models.FindUserByEmailAndAudience(ts.API.db, "test@example.com", ts.Config.JWT.Aud) - require.NoError(ts.T(), err) - s, err := models.FindSessionByUserID(ts.API.db, u.ID) - require.NoError(ts.T(), err) + user, session := ts.setupUserAndSession() + var secondarySession *models.Session - factors, err := models.FindFactorsByUser(ts.API.db, u) + factors, err := models.FindFactorsByUser(ts.API.db, user) require.NoError(ts.T(), err, "error finding factors") f := factors[0] secondarySession, err = models.NewSession() require.NoError(ts.T(), err, "Error creating test session") - secondarySession.UserID = u.ID + secondarySession.UserID = user.ID secondarySession.FactorID = &f.ID require.NoError(ts.T(), ts.API.db.Create(secondarySession), "Error saving test session") @@ -356,7 +360,7 @@ func (ts *MFATestSuite) TestUnenrollUnverifiedFactor() { var buffer bytes.Buffer - token, _, err := generateAccessToken(ts.API.db, u, &s.ID, &ts.Config.JWT) + token, _, err := generateAccessToken(ts.API.db, user, &session.ID, &ts.Config.JWT) require.NoError(ts.T(), err) require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(map[string]interface{}{ "factor_id": f.ID, @@ -369,7 +373,7 @@ func (ts *MFATestSuite) TestUnenrollUnverifiedFactor() { require.Equal(ts.T(), http.StatusOK, w.Code) _, err = models.FindFactorByFactorID(ts.API.db, f.ID) require.EqualError(ts.T(), err, models.FactorNotFoundError{}.Error()) - session, _ := models.FindSessionByID(ts.API.db, secondarySession.ID, false) + session, _ = models.FindSessionByID(ts.API.db, secondarySession.ID, false) require.Equal(ts.T(), models.AAL1.String(), session.GetAAL()) require.Nil(ts.T(), session.FactorID) From 56fd7439e5685fe133f38b8a1ece7c759ceec285 Mon Sep 17 00:00:00 2001 From: "joel@joellee.org" Date: Tue, 28 Nov 2023 18:28:00 +0800 Subject: [PATCH 3/5] refactor: pull out verify --- internal/api/mfa_test.go | 93 ++++++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 42 deletions(-) diff --git a/internal/api/mfa_test.go b/internal/api/mfa_test.go index 9bd20a6de3..92b0895c40 100644 --- a/internal/api/mfa_test.go +++ b/internal/api/mfa_test.go @@ -5,6 +5,7 @@ import ( "context" "encoding/json" "fmt" + "github.com/gofrs/uuid" "net/http" "net/http/httptest" "strings" @@ -25,11 +26,12 @@ import ( type MFATestSuite struct { suite.Suite - API *API - Config *conf.GlobalConfiguration - TestDomain string - TestEmail string - TestOTPKey *otp.Key + API *API + Config *conf.GlobalConfiguration + TestDomain string + TestEmail string + TestOTPKey *otp.Key + TestPassword string } func TestMFA(t *testing.T) { @@ -67,6 +69,7 @@ func (ts *MFATestSuite) SetupTest() { testDomain := strings.Split(testEmail, "@")[1] ts.TestDomain = testDomain ts.TestEmail = testEmail + ts.TestPassword = "password" key, err := totp.Generate(totp.GenerateOpts{ Issuer: ts.TestDomain, @@ -381,14 +384,16 @@ func (ts *MFATestSuite) TestUnenrollUnverifiedFactor() { // Integration Tests func (ts *MFATestSuite) TestSessionsMaintainAALOnRefresh() { - email := "test1@example.com" - password := "test123" - token := signUpAndVerify(ts, email, password) + resp := signUpAndVerify(ts, ts.TestEmail, ts.TestPassword) + accessTokenResp := &AccessTokenResponse{} + require.NoError(ts.T(), json.NewDecoder(resp.Body).Decode(&accessTokenResp)) + ts.Config.Security.RefreshTokenRotationEnabled = true var buffer bytes.Buffer require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(map[string]interface{}{ - "refresh_token": token.RefreshToken, + "refresh_token": accessTokenResp.RefreshToken, })) + req := httptest.NewRequest(http.MethodPost, "http://localhost/token?grant_type=refresh_token", &buffer) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() @@ -407,14 +412,15 @@ func (ts *MFATestSuite) TestSessionsMaintainAALOnRefresh() { // Performing MFA Verification followed by a sign in should return an AAL1 session and an AAL2 session func (ts *MFATestSuite) TestMFAFollowedByPasswordSignIn() { - email := "test1@example.com" - password := "test123" - token := signUpAndVerify(ts, email, password) + resp := signUpAndVerify(ts, ts.TestEmail, ts.TestPassword) + accessTokenResp := &AccessTokenResponse{} + require.NoError(ts.T(), json.NewDecoder(resp.Body).Decode(&accessTokenResp)) + ts.Config.Security.RefreshTokenRotationEnabled = true var buffer bytes.Buffer require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(map[string]interface{}{ - "email": email, - "password": password, + "email": ts.TestEmail, + "password": ts.TestPassword, })) req := httptest.NewRequest(http.MethodPost, "http://localhost/token?grant_type=password", &buffer) req.Header.Set("Content-Type", "application/json") @@ -429,7 +435,7 @@ func (ts *MFATestSuite) TestMFAFollowedByPasswordSignIn() { ctx, err = ts.API.maybeLoadUserOrSession(ctx) require.NoError(ts.T(), err) require.Equal(ts.T(), models.AAL1.String(), getSession(ctx).GetAAL()) - session, err := models.FindSessionByUserID(ts.API.db, token.User.ID) + session, err := models.FindSessionByUserID(ts.API.db, accessTokenResp.User.ID) require.NoError(ts.T(), err) require.True(ts.T(), session.IsAAL2()) } @@ -454,12 +460,12 @@ func signUp(ts *MFATestSuite, email, password string) (signUpResp AccessTokenRes return data } -func signUpAndVerify(ts *MFATestSuite, email, password string) (verifyResp *AccessTokenResponse) { +func signUpAndVerify(ts *MFATestSuite, email, password string) *httptest.ResponseRecorder { signUpResp := signUp(ts, email, password) - verifyResp = enrollAndVerify(ts, signUpResp.User, signUpResp.Token) + resp := enrollAndVerify(ts, signUpResp.User, signUpResp.Token) - return verifyResp + return resp } @@ -477,26 +483,7 @@ func enroll(ts *MFATestSuite, token, friendlyName, factorType, issuer string, ex return w } - -func enrollAndVerify(ts *MFATestSuite, user *models.User, token string) (verifyResp *AccessTokenResponse) { - w := enroll(ts, token, "", models.TOTP, ts.TestDomain, http.StatusOK) - enrollResp := EnrollFactorResponse{} - require.NoError(ts.T(), json.NewDecoder(w.Body).Decode(&enrollResp)) - factorID := enrollResp.ID - - // Challenge - var challengeBuffer bytes.Buffer - x := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost/factors/%s/challenge", factorID), &challengeBuffer) - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) - req.Header.Set("Content-Type", "application/json") - ts.API.handler.ServeHTTP(x, req) - require.Equal(ts.T(), http.StatusOK, x.Code) - challengeResp := EnrollFactorResponse{} - require.NoError(ts.T(), json.NewDecoder(x.Body).Decode(&challengeResp)) - challengeID := challengeResp.ID - - // Verify +func verify(ts *MFATestSuite, challengeID, factorID uuid.UUID, token string, expectedCode int) *httptest.ResponseRecorder { var verifyBuffer bytes.Buffer y := httptest.NewRecorder() @@ -516,13 +503,35 @@ func enrollAndVerify(ts *MFATestSuite, user *models.User, token string) (verifyR "challenge_id": challengeID, "code": code, })) - req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("/factors/%s/verify", factorID), &verifyBuffer) + req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/factors/%s/verify", factorID), &verifyBuffer) req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) req.Header.Set("Content-Type", "application/json") ts.API.handler.ServeHTTP(y, req) require.Equal(ts.T(), http.StatusOK, y.Code) - verifyResp = &AccessTokenResponse{} - require.NoError(ts.T(), json.NewDecoder(y.Body).Decode(&verifyResp)) - return verifyResp + return y +} + +func enrollAndVerify(ts *MFATestSuite, user *models.User, token string) *httptest.ResponseRecorder { + w := enroll(ts, token, "", models.TOTP, ts.TestDomain, http.StatusOK) + enrollResp := EnrollFactorResponse{} + require.NoError(ts.T(), json.NewDecoder(w.Body).Decode(&enrollResp)) + factorID := enrollResp.ID + + // Challenge + var challengeBuffer bytes.Buffer + x := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost/factors/%s/challenge", factorID), &challengeBuffer) + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + req.Header.Set("Content-Type", "application/json") + ts.API.handler.ServeHTTP(x, req) + require.Equal(ts.T(), http.StatusOK, x.Code) + challengeResp := EnrollFactorResponse{} + require.NoError(ts.T(), json.NewDecoder(x.Body).Decode(&challengeResp)) + challengeID := challengeResp.ID + + // Verify + y := verify(ts, challengeID, factorID, token, http.StatusOK) + + return y } From eecc993f1c842d2ca8d9085de355acdfcb14dca0 Mon Sep 17 00:00:00 2001 From: "joel@joellee.org" Date: Wed, 29 Nov 2023 01:08:04 +0800 Subject: [PATCH 4/5] refactor: update naming --- internal/api/mfa_test.go | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/internal/api/mfa_test.go b/internal/api/mfa_test.go index 92b0895c40..9cbc1f71a1 100644 --- a/internal/api/mfa_test.go +++ b/internal/api/mfa_test.go @@ -129,7 +129,7 @@ func (ts *MFATestSuite) TestEnrollFactor() { for _, c := range cases { ts.Run(c.desc, func() { - w := enroll(ts, token, c.friendlyName, c.factorType, c.issuer, c.expectedCode) + w := performEnrollFlow(ts, token, c.friendlyName, c.factorType, c.issuer, c.expectedCode) factors, err := models.FindFactorsByUser(ts.API.db, user) ts.Require().NoError(err) @@ -278,7 +278,6 @@ func (ts *MFATestSuite) setupUserAndSession() (*models.User, *models.Session) { } func (ts *MFATestSuite) TestUnenrollVerifiedFactor() { - user, session := ts.setupUserAndSession() cases := []struct { @@ -384,7 +383,7 @@ func (ts *MFATestSuite) TestUnenrollUnverifiedFactor() { // Integration Tests func (ts *MFATestSuite) TestSessionsMaintainAALOnRefresh() { - resp := signUpAndVerify(ts, ts.TestEmail, ts.TestPassword) + resp := performTestSignupAndVerify(ts, ts.TestEmail, ts.TestPassword) accessTokenResp := &AccessTokenResponse{} require.NoError(ts.T(), json.NewDecoder(resp.Body).Decode(&accessTokenResp)) @@ -412,7 +411,7 @@ func (ts *MFATestSuite) TestSessionsMaintainAALOnRefresh() { // Performing MFA Verification followed by a sign in should return an AAL1 session and an AAL2 session func (ts *MFATestSuite) TestMFAFollowedByPasswordSignIn() { - resp := signUpAndVerify(ts, ts.TestEmail, ts.TestPassword) + resp := performTestSignupAndVerify(ts, ts.TestEmail, ts.TestPassword) accessTokenResp := &AccessTokenResponse{} require.NoError(ts.T(), json.NewDecoder(resp.Body).Decode(&accessTokenResp)) @@ -460,16 +459,16 @@ func signUp(ts *MFATestSuite, email, password string) (signUpResp AccessTokenRes return data } -func signUpAndVerify(ts *MFATestSuite, email, password string) *httptest.ResponseRecorder { +func performTestSignupAndVerify(ts *MFATestSuite, email, password string) *httptest.ResponseRecorder { signUpResp := signUp(ts, email, password) - resp := enrollAndVerify(ts, signUpResp.User, signUpResp.Token) + resp := performEnrollAndVerify(ts, signUpResp.User, signUpResp.Token) return resp } -func enroll(ts *MFATestSuite, token, friendlyName, factorType, issuer string, expectedCode int) *httptest.ResponseRecorder { +func performEnrollFlow(ts *MFATestSuite, token, friendlyName, factorType, issuer string, expectedCode int) *httptest.ResponseRecorder { var buffer bytes.Buffer w := httptest.NewRecorder() require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(map[string]string{"friendly_name": friendlyName, "factor_type": factorType, "issuer": issuer})) @@ -483,7 +482,7 @@ func enroll(ts *MFATestSuite, token, friendlyName, factorType, issuer string, ex return w } -func verify(ts *MFATestSuite, challengeID, factorID uuid.UUID, token string, expectedCode int) *httptest.ResponseRecorder { +func performVerifyFlow(ts *MFATestSuite, challengeID, factorID uuid.UUID, token string, expectedCode int) *httptest.ResponseRecorder { var verifyBuffer bytes.Buffer y := httptest.NewRecorder() @@ -512,26 +511,26 @@ func verify(ts *MFATestSuite, challengeID, factorID uuid.UUID, token string, exp return y } -func enrollAndVerify(ts *MFATestSuite, user *models.User, token string) *httptest.ResponseRecorder { - w := enroll(ts, token, "", models.TOTP, ts.TestDomain, http.StatusOK) +func performEnrollAndVerify(ts *MFATestSuite, user *models.User, token string) *httptest.ResponseRecorder { + w := performEnrollFlow(ts, token, "", models.TOTP, ts.TestDomain, http.StatusOK) enrollResp := EnrollFactorResponse{} require.NoError(ts.T(), json.NewDecoder(w.Body).Decode(&enrollResp)) factorID := enrollResp.ID // Challenge var challengeBuffer bytes.Buffer - x := httptest.NewRecorder() + w = httptest.NewRecorder() req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost/factors/%s/challenge", factorID), &challengeBuffer) req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) req.Header.Set("Content-Type", "application/json") - ts.API.handler.ServeHTTP(x, req) - require.Equal(ts.T(), http.StatusOK, x.Code) + ts.API.handler.ServeHTTP(w, req) + require.Equal(ts.T(), http.StatusOK, w.Code) challengeResp := EnrollFactorResponse{} - require.NoError(ts.T(), json.NewDecoder(x.Body).Decode(&challengeResp)) + require.NoError(ts.T(), json.NewDecoder(w.Body).Decode(&challengeResp)) challengeID := challengeResp.ID // Verify - y := verify(ts, challengeID, factorID, token, http.StatusOK) + y := performVerifyFlow(ts, challengeID, factorID, token, http.StatusOK) return y } From aa4cdaa4c8785e60b528281f1ab9cc22b09830e8 Mon Sep 17 00:00:00 2001 From: "joel@joellee.org" Date: Wed, 29 Nov 2023 01:25:59 +0800 Subject: [PATCH 5/5] refactor: remove unneeded setup, add performChallengeFlow --- internal/api/mfa_test.go | 61 ++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 33 deletions(-) diff --git a/internal/api/mfa_test.go b/internal/api/mfa_test.go index 9cbc1f71a1..8d4d6ef73d 100644 --- a/internal/api/mfa_test.go +++ b/internal/api/mfa_test.go @@ -32,6 +32,8 @@ type MFATestSuite struct { TestEmail string TestOTPKey *otp.Key TestPassword string + TestUser *models.User + TestSession *models.Session } func TestMFA(t *testing.T) { @@ -62,6 +64,9 @@ func (ts *MFATestSuite) SetupTest() { s.FactorID = &f.ID require.NoError(ts.T(), ts.API.db.Create(s), "Error saving test session") + ts.TestUser = u + ts.TestSession = s + // Generate TOTP related settings emailValue, err := u.Email.Value() require.NoError(ts.T(), err) @@ -162,13 +167,7 @@ func (ts *MFATestSuite) TestChallengeFactor() { token, _, err := generateAccessToken(ts.API.db, u, nil, &ts.Config.JWT) require.NoError(ts.T(), err, "Error generating access token") - var buffer bytes.Buffer - req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost/factors/%s/challenge", f.ID), &buffer) - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) - - w := httptest.NewRecorder() - ts.API.handler.ServeHTTP(w, req) + w := performChallengeFlow(ts, f.ID, token) require.Equal(ts.T(), http.StatusOK, w.Code) } @@ -269,16 +268,7 @@ func (ts *MFATestSuite) TestMFAVerifyFactor() { } } -func (ts *MFATestSuite) setupUserAndSession() (*models.User, *models.Session) { - user, err := models.FindUserByEmailAndAudience(ts.API.db, ts.TestEmail, ts.Config.JWT.Aud) - require.NoError(ts.T(), err) - session, err := models.FindSessionByUserID(ts.API.db, user.ID) - require.NoError(ts.T(), err) - return user, session -} - func (ts *MFATestSuite) TestUnenrollVerifiedFactor() { - user, session := ts.setupUserAndSession() cases := []struct { desc string @@ -300,17 +290,17 @@ func (ts *MFATestSuite) TestUnenrollVerifiedFactor() { ts.Run(v.desc, func() { // Create User if v.isAAL2 { - session.UpdateAssociatedAAL(ts.API.db, models.AAL2.String()) + ts.TestSession.UpdateAssociatedAAL(ts.API.db, models.AAL2.String()) } var secondarySession *models.Session // Create Session to test behaviour which downgrades other sessions - factors, err := models.FindFactorsByUser(ts.API.db, user) + factors, err := models.FindFactorsByUser(ts.API.db, ts.TestUser) require.NoError(ts.T(), err, "error finding factors") f := factors[0] secondarySession, err = models.NewSession() require.NoError(ts.T(), err, "Error creating test session") - secondarySession.UserID = user.ID + secondarySession.UserID = ts.TestUser.ID secondarySession.FactorID = &f.ID require.NoError(ts.T(), ts.API.db.Create(secondarySession), "Error saving test session") @@ -322,7 +312,7 @@ func (ts *MFATestSuite) TestUnenrollVerifiedFactor() { var buffer bytes.Buffer - token, _, err := generateAccessToken(ts.API.db, user, &session.ID, &ts.Config.JWT) + token, _, err := generateAccessToken(ts.API.db, ts.TestUser, &ts.TestSession.ID, &ts.Config.JWT) require.NoError(ts.T(), err) w := httptest.NewRecorder() @@ -345,15 +335,13 @@ func (ts *MFATestSuite) TestUnenrollVerifiedFactor() { } func (ts *MFATestSuite) TestUnenrollUnverifiedFactor() { - user, session := ts.setupUserAndSession() - var secondarySession *models.Session - factors, err := models.FindFactorsByUser(ts.API.db, user) + factors, err := models.FindFactorsByUser(ts.API.db, ts.TestUser) require.NoError(ts.T(), err, "error finding factors") f := factors[0] secondarySession, err = models.NewSession() require.NoError(ts.T(), err, "Error creating test session") - secondarySession.UserID = user.ID + secondarySession.UserID = ts.TestUser.ID secondarySession.FactorID = &f.ID require.NoError(ts.T(), ts.API.db.Create(secondarySession), "Error saving test session") @@ -362,7 +350,7 @@ func (ts *MFATestSuite) TestUnenrollUnverifiedFactor() { var buffer bytes.Buffer - token, _, err := generateAccessToken(ts.API.db, user, &session.ID, &ts.Config.JWT) + token, _, err := generateAccessToken(ts.API.db, ts.TestUser, &ts.TestSession.ID, &ts.Config.JWT) require.NoError(ts.T(), err) require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(map[string]interface{}{ "factor_id": f.ID, @@ -375,7 +363,7 @@ func (ts *MFATestSuite) TestUnenrollUnverifiedFactor() { require.Equal(ts.T(), http.StatusOK, w.Code) _, err = models.FindFactorByFactorID(ts.API.db, f.ID) require.EqualError(ts.T(), err, models.FactorNotFoundError{}.Error()) - session, _ = models.FindSessionByID(ts.API.db, secondarySession.ID, false) + session, _ := models.FindSessionByID(ts.API.db, secondarySession.ID, false) require.Equal(ts.T(), models.AAL1.String(), session.GetAAL()) require.Nil(ts.T(), session.FactorID) @@ -511,6 +499,18 @@ func performVerifyFlow(ts *MFATestSuite, challengeID, factorID uuid.UUID, token return y } +func performChallengeFlow(ts *MFATestSuite, factorID uuid.UUID, token string) *httptest.ResponseRecorder { + var challengeBuffer bytes.Buffer + w := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost/factors/%s/challenge", factorID), &challengeBuffer) + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + req.Header.Set("Content-Type", "application/json") + ts.API.handler.ServeHTTP(w, req) + require.Equal(ts.T(), http.StatusOK, w.Code) + return w + +} + func performEnrollAndVerify(ts *MFATestSuite, user *models.User, token string) *httptest.ResponseRecorder { w := performEnrollFlow(ts, token, "", models.TOTP, ts.TestDomain, http.StatusOK) enrollResp := EnrollFactorResponse{} @@ -518,13 +518,8 @@ func performEnrollAndVerify(ts *MFATestSuite, user *models.User, token string) * factorID := enrollResp.ID // Challenge - var challengeBuffer bytes.Buffer - w = httptest.NewRecorder() - req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost/factors/%s/challenge", factorID), &challengeBuffer) - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) - req.Header.Set("Content-Type", "application/json") - ts.API.handler.ServeHTTP(w, req) - require.Equal(ts.T(), http.StatusOK, w.Code) + w = performChallengeFlow(ts, factorID, token) + challengeResp := EnrollFactorResponse{} require.NoError(ts.T(), json.NewDecoder(w.Body).Decode(&challengeResp)) challengeID := challengeResp.ID