From f965c800aa517c8993073fbd0d6c26d732b86459 Mon Sep 17 00:00:00 2001 From: Marwen Abid Date: Wed, 4 Dec 2024 09:44:14 -0800 Subject: [PATCH] [SDP-1311] Make Dashboard User E-mails case insensitive (#485) * SDP-1311 Make E-mails case insensitive * SDP-1311 Address PR feedback --- ...1-29.0-sanitize-email-auth_users-table.sql | 7 ++ stellar-auth/pkg/auth/auth.go | 2 +- stellar-auth/pkg/auth/auth_test.go | 2 +- stellar-auth/pkg/auth/authenticator.go | 40 ++++--- stellar-auth/pkg/auth/authenticator_test.go | 97 ++++++++++++--- stellar-auth/pkg/auth/fixtures.go | 2 + stellar-auth/pkg/auth/manager.go | 7 +- stellar-auth/pkg/auth/manager_test.go | 110 +++++++++++++++--- stellar-auth/pkg/cli/add_user.go | 2 +- stellar-auth/pkg/cli/add_user_test.go | 12 +- 10 files changed, 224 insertions(+), 57 deletions(-) create mode 100644 db/migrations/auth-migrations/2024-11-29.0-sanitize-email-auth_users-table.sql diff --git a/db/migrations/auth-migrations/2024-11-29.0-sanitize-email-auth_users-table.sql b/db/migrations/auth-migrations/2024-11-29.0-sanitize-email-auth_users-table.sql new file mode 100644 index 000000000..c17d6d425 --- /dev/null +++ b/db/migrations/auth-migrations/2024-11-29.0-sanitize-email-auth_users-table.sql @@ -0,0 +1,7 @@ +-- +migrate Up +UPDATE auth_users +SET + email = LOWER(TRIM(email)); + +-- +migrate Down +-- No down migration needed as email sanitization cannot be reversed \ No newline at end of file diff --git a/stellar-auth/pkg/auth/auth.go b/stellar-auth/pkg/auth/auth.go index 42d509fda..2e8e5e568 100644 --- a/stellar-auth/pkg/auth/auth.go +++ b/stellar-auth/pkg/auth/auth.go @@ -147,7 +147,7 @@ func (am *defaultAuthManager) AnyRolesInTokenUser(ctx context.Context, tokenStri func (am *defaultAuthManager) CreateUser(ctx context.Context, user *User, password string) (*User, error) { user, err := am.authenticator.CreateUser(ctx, user, password) if err != nil { - return nil, fmt.Errorf("error creating user: %w", err) + return nil, fmt.Errorf("creating user: %w", err) } return user, nil diff --git a/stellar-auth/pkg/auth/auth_test.go b/stellar-auth/pkg/auth/auth_test.go index 9f99132e7..b596ae3ec 100644 --- a/stellar-auth/pkg/auth/auth_test.go +++ b/stellar-auth/pkg/auth/auth_test.go @@ -560,7 +560,7 @@ func Test_AuthManager_CreateUser(t *testing.T) { u, err := authManager.CreateUser(ctx, user, password) - assert.EqualError(t, err, "error creating user: unexpected error") + assert.EqualError(t, err, "creating user: unexpected error") assert.Nil(t, u) }) diff --git a/stellar-auth/pkg/auth/authenticator.go b/stellar-auth/pkg/auth/authenticator.go index 63f2b58ea..811aa25b2 100644 --- a/stellar-auth/pkg/auth/authenticator.go +++ b/stellar-auth/pkg/auth/authenticator.go @@ -59,9 +59,12 @@ type authUser struct { } func (a *defaultAuthenticator) ValidateCredentials(ctx context.Context, email, password string) (*User, error) { + email = strings.TrimSpace(strings.ToLower(email)) + const query = ` SELECT u.id, + u.email, u.first_name, u.last_name, u.encrypted_password @@ -91,7 +94,7 @@ func (a *defaultAuthenticator) ValidateCredentials(ctx context.Context, email, p return &User{ ID: au.ID, - Email: email, + Email: au.Email, FirstName: au.FirstName, LastName: au.LastName, }, nil @@ -100,8 +103,8 @@ func (a *defaultAuthenticator) ValidateCredentials(ctx context.Context, email, p // CreateUser creates a user in the database. If a empty password is passed by parameter, a random password is generated, // so the user can go through the ForgotPassword flow. func (a *defaultAuthenticator) CreateUser(ctx context.Context, user *User, password string) (*User, error) { - if err := user.Validate(); err != nil { - return nil, fmt.Errorf("error validating user fields: %w", err) + if err := user.SanitizeAndValidate(); err != nil { + return nil, fmt.Errorf("validating user fields: %w", err) } // In case no password is passed we generate a random OTP (One Time Password) @@ -109,19 +112,19 @@ func (a *defaultAuthenticator) CreateUser(ctx context.Context, user *User, passw // Random length pasword randomNumber, err := rand.Int(rand.Reader, big.NewInt(MaxPasswordLength-MinPasswordLength+1)) if err != nil { - return nil, fmt.Errorf("error generating random number in create user: %w", err) + return nil, fmt.Errorf("generating random number in create user: %w", err) } passwordLength := int(randomNumber.Int64() + MinPasswordLength) password, err = utils.StringWithCharset(passwordLength, utils.PasswordCharset) if err != nil { - return nil, fmt.Errorf("error generating random password string in create user: %w", err) + return nil, fmt.Errorf("generating random password string in create user: %w", err) } } encryptedPassword, err := a.passwordEncrypter.Encrypt(ctx, password) if err != nil { - return nil, fmt.Errorf("error encrypting password: %w", err) + return nil, fmt.Errorf("encrypting password: %w", err) } const query = ` @@ -138,7 +141,7 @@ func (a *defaultAuthenticator) CreateUser(ctx context.Context, user *User, passw if pqError, ok := err.(*pq.Error); ok && pqError.Constraint == "auth_users_email_key" { return nil, ErrUserEmailAlreadyExists } - return nil, fmt.Errorf("error inserting user: %w", err) + return nil, fmt.Errorf("inserting user: %w", err) } user.ID = userID @@ -148,6 +151,10 @@ func (a *defaultAuthenticator) CreateUser(ctx context.Context, user *User, passw } func (a *defaultAuthenticator) UpdateUser(ctx context.Context, ID, firstName, lastName, email, password string) error { + firstName = strings.TrimSpace(firstName) + lastName = strings.TrimSpace(lastName) + email = strings.TrimSpace(strings.ToLower(email)) + if firstName == "" && lastName == "" && email == "" && password == "" { return fmt.Errorf("provide at least one of these values: firstName, lastName, email or password") } @@ -174,7 +181,7 @@ func (a *defaultAuthenticator) UpdateUser(ctx context.Context, ID, firstName, la if email != "" { if err := utils.ValidateEmail(email); err != nil { - return fmt.Errorf("error validating email: %w", err) + return fmt.Errorf("validating email: %w", err) } fields = append(fields, "email = ?") @@ -185,7 +192,7 @@ func (a *defaultAuthenticator) UpdateUser(ctx context.Context, ID, firstName, la encryptedPassword, err := a.passwordEncrypter.Encrypt(ctx, password) if err != nil { if !errors.Is(err, ErrPasswordTooShort) { - return fmt.Errorf("error encrypting password: %w", err) + return fmt.Errorf("encrypting password: %w", err) } return err } @@ -199,12 +206,12 @@ func (a *defaultAuthenticator) UpdateUser(ctx context.Context, ID, firstName, la res, err := a.dbConnectionPool.ExecContext(ctx, query, args...) if err != nil { - return fmt.Errorf("error updating user in the database: %w", err) + return fmt.Errorf("updating user in the database: %w", err) } numRowsAffected, err := res.RowsAffected() if err != nil { - return fmt.Errorf("error getting the number of rows affected: %w", err) + return fmt.Errorf("getting the number of rows affected: %w", err) } if numRowsAffected == 0 { return ErrNoRowsAffected @@ -252,13 +259,14 @@ func (a *defaultAuthenticator) DeactivateUser(ctx context.Context, userID string } func (a *defaultAuthenticator) ForgotPassword(ctx context.Context, sqlExec db.SQLExecuter, email string) (string, error) { + email = strings.TrimSpace(strings.ToLower(email)) if email == "" { - return "", fmt.Errorf("error generating user reset password token: email cannot be empty") + return "", fmt.Errorf("generating user reset password token: email cannot be empty") } resetToken, err := utils.StringWithCharset(resetTokenLength, utils.DefaultCharset) if err != nil { - return "", fmt.Errorf("error generating random reset token in forgot password: %w", err) + return "", fmt.Errorf("generating random reset token in forgot password: %w", err) } checkValidTokenQuery := ` @@ -274,7 +282,7 @@ func (a *defaultAuthenticator) ForgotPassword(ctx context.Context, sqlExec db.SQ var hasValidToken bool err = sqlExec.GetContext(ctx, &hasValidToken, checkValidTokenQuery, email) if err != nil { - return "", fmt.Errorf("error checking if user has valid token: %w", err) + return "", fmt.Errorf("checking if user has valid token: %w", err) } if hasValidToken { @@ -291,11 +299,11 @@ func (a *defaultAuthenticator) ForgotPassword(ctx context.Context, sqlExec db.SQ ` result, err := sqlExec.ExecContext(ctx, q, email, resetToken) if err != nil { - return "", fmt.Errorf("error inserting user reset password token in the database: %w", err) + return "", fmt.Errorf("inserting user reset password token in the database: %w", err) } rowsAffected, err := result.RowsAffected() if err != nil { - return "", fmt.Errorf("error getting rows affected inserting user reset password token in the database: %w", err) + return "", fmt.Errorf("getting rows affected inserting user reset password token in the database: %w", err) } if rowsAffected == 0 { return "", ErrUserNotFound diff --git a/stellar-auth/pkg/auth/authenticator_test.go b/stellar-auth/pkg/auth/authenticator_test.go index 4766ba164..5d9ed9e43 100644 --- a/stellar-auth/pkg/auth/authenticator_test.go +++ b/stellar-auth/pkg/auth/authenticator_test.go @@ -5,6 +5,7 @@ import ( "database/sql" "errors" "fmt" + "strings" "testing" "time" @@ -135,6 +136,30 @@ func Test_DefaultAuthenticator_ValidateCredential(t *testing.T) { assert.Equal(t, randUser.LastName, user.LastName) }) + t.Run("returns user successfully - case-insensitive", func(t *testing.T) { + encryptedPassword := "encryptedpassword" + + passwordEncrypterMock. + On("Encrypt", ctx, mock.AnythingOfType("string")). + Return(encryptedPassword, nil). + Once() + + randUser := CreateRandomAuthUserFixture(t, ctx, dbConnectionPool, passwordEncrypterMock, false) + + passwordEncrypterMock. + On("ComparePassword", ctx, randUser.EncryptedPassword, randUser.Password). + Return(true, nil). + Once() + + uppercaseEmail := strings.ToUpper(randUser.Email) + user, err := authenticator.ValidateCredentials(ctx, uppercaseEmail, randUser.Password) + require.NoError(t, err) + + assert.Equal(t, randUser.Email, user.Email) + assert.Equal(t, randUser.ID, user.ID) + assert.Equal(t, randUser.FirstName, user.FirstName) + assert.Equal(t, randUser.LastName, user.LastName) + }) passwordEncrypterMock.AssertExpectations(t) } @@ -163,27 +188,27 @@ func Test_DefaultAuthenticator_CreateUser(t *testing.T) { u, err := authenticator.CreateUser(ctx, user, password) assert.Nil(t, u) - assert.EqualError(t, err, "error validating user fields: email is required") + assert.EqualError(t, err, "validating user fields: email is required") user.Email = "invalid" u, err = authenticator.CreateUser(ctx, user, password) assert.Nil(t, u) - assert.EqualError(t, err, `error validating user fields: email is invalid: the provided email "invalid" is not valid`) + assert.EqualError(t, err, `validating user fields: email is invalid: the provided email "invalid" is not valid`) // First name user.Email = "email@email.com" u, err = authenticator.CreateUser(ctx, user, password) assert.Nil(t, u) - assert.EqualError(t, err, "error validating user fields: first name is required") + assert.EqualError(t, err, "validating user fields: first name is required") // Last name user.FirstName = "First" u, err = authenticator.CreateUser(ctx, user, password) assert.Nil(t, u) - assert.EqualError(t, err, "error validating user fields: last name is required") + assert.EqualError(t, err, "validating user fields: last name is required") }) t.Run("returns error when password is invalid", func(t *testing.T) { @@ -203,7 +228,7 @@ func Test_DefaultAuthenticator_CreateUser(t *testing.T) { u, err := authenticator.CreateUser(ctx, user, password) assert.Nil(t, u) - assert.EqualError(t, err, fmt.Sprintf("error encrypting password: password should have at least %d characters", MinPasswordLength)) + assert.EqualError(t, err, fmt.Sprintf("encrypting password: password should have at least %d characters", MinPasswordLength)) passwordEncrypterMock. On("Encrypt", ctx, password). @@ -213,7 +238,7 @@ func Test_DefaultAuthenticator_CreateUser(t *testing.T) { u, err = authenticator.CreateUser(ctx, user, password) assert.Nil(t, u) - assert.EqualError(t, err, "error encrypting password: unexpected error") + assert.EqualError(t, err, "encrypting password: unexpected error") }) t.Run("returns error when user is duplicated", func(t *testing.T) { @@ -300,6 +325,35 @@ func Test_DefaultAuthenticator_CreateUser(t *testing.T) { assert.Equal(t, "encryptedpassword", encryptedPassword) }) + t.Run("creates a new user correctly - case-insensitive", func(t *testing.T) { + user := &User{ + Email: " EMAIL-TEST2@email.com", + FirstName: " First", + LastName: " Last", + } + + password := "mysecret" + + passwordEncrypterMock. + On("Encrypt", ctx, password). + Return("encryptedpassword", nil). + Once() + + u, err := authenticator.CreateUser(ctx, user, password) + require.NoError(t, err) + + const query = "SELECT id, email, first_name, last_name FROM auth_users WHERE email = $1" + + var newUser User + err = dbConnectionPool.QueryRowxContext(ctx, query, user.Email).Scan(&newUser.ID, &newUser.Email, &newUser.FirstName, &newUser.LastName) + require.NoError(t, err) + + assert.Equal(t, u.ID, newUser.ID) + assert.Equal(t, strings.ToLower(strings.TrimSpace(u.Email)), newUser.Email) + assert.Equal(t, strings.TrimSpace(u.FirstName), newUser.FirstName) + assert.Equal(t, strings.TrimSpace(u.LastName), newUser.LastName) + }) + passwordEncrypterMock.AssertExpectations(t) } @@ -504,7 +558,7 @@ func Test_DefaultAuthenticator_ForgotPassword(t *testing.T) { t.Run("Should return an error if the email is empty", func(t *testing.T) { resetToken, err := authenticator.ForgotPassword(ctx, dbConnectionPool, "") - assert.EqualError(t, err, "error generating user reset password token: email cannot be empty") + assert.EqualError(t, err, "generating user reset password token: email cannot be empty") assert.Empty(t, resetToken) }) @@ -590,6 +644,23 @@ func Test_DefaultAuthenticator_ForgotPassword(t *testing.T) { assert.NotEmpty(t, resetToken) }) + t.Run("Should return reset token with a valid user - case-insensitive", func(t *testing.T) { + encryptedPassword := "encryptedpassword" + + passwordEncrypterMock. + On("Encrypt", ctx, mock.AnythingOfType("string")). + Return(encryptedPassword, nil). + Once() + + randUser := CreateRandomAuthUserFixture(t, ctx, dbConnectionPool, passwordEncrypterMock, false) + + uppercaseEmail := strings.ToUpper(randUser.Email) + resetToken, err := authenticator.ForgotPassword(ctx, dbConnectionPool, uppercaseEmail) + require.NoError(t, err) + + assert.NotEmpty(t, resetToken) + }) + passwordEncrypterMock.AssertExpectations(t) } @@ -753,7 +824,7 @@ func Test_DefaultAuthenticator_UpdateUser(t *testing.T) { t.Run("returns error when email is invalid", func(t *testing.T) { err := authenticator.UpdateUser(ctx, "user-id", "", "", "invalid", "") - assert.EqualError(t, err, `error validating email: the provided email "invalid" is not valid`) + assert.EqualError(t, err, `validating email: the provided email "invalid" is not valid`) }) t.Run("returns error when password is too short", func(t *testing.T) { @@ -777,7 +848,7 @@ func Test_DefaultAuthenticator_UpdateUser(t *testing.T) { Once() err := authenticator.UpdateUser(ctx, "user-id", "", "", "", "short") - assert.EqualError(t, err, "error encrypting password: unexpected error") + assert.EqualError(t, err, "encrypting password: unexpected error") }) t.Run("updates first name successfully", func(t *testing.T) { @@ -872,7 +943,7 @@ func Test_DefaultAuthenticator_UpdateUser(t *testing.T) { }) t.Run("updates all fields successfully", func(t *testing.T) { - firstName, lastName, email, password := "FirstName", "LastName", "new_email@email.com", "newpassword" + firstName, lastName, email, password := "FirstName ", " LastName ", " new_EMail@email.com", "newpassword" passwordEncrypterMock. On("Encrypt", ctx, mock.AnythingOfType("string")). @@ -893,9 +964,9 @@ func Test_DefaultAuthenticator_UpdateUser(t *testing.T) { u := getUser(t, ctx, randUser.ID) - assert.Equal(t, firstName, u.FirstName) - assert.Equal(t, lastName, u.LastName) - assert.Equal(t, email, u.Email) + assert.Equal(t, "FirstName", u.FirstName) + assert.Equal(t, "LastName", u.LastName) + assert.Equal(t, "new_email@email.com", u.Email) assert.Equal(t, "newpassowrdencrypted", u.EncryptedPassword) }) } diff --git a/stellar-auth/pkg/auth/fixtures.go b/stellar-auth/pkg/auth/fixtures.go index 724b4efe8..028a5b28e 100644 --- a/stellar-auth/pkg/auth/fixtures.go +++ b/stellar-auth/pkg/auth/fixtures.go @@ -5,6 +5,7 @@ import ( "crypto/rand" "fmt" "math/big" + "strings" "testing" "time" @@ -55,6 +56,7 @@ func randStringRunes(t *testing.T, n int) string { func CreateRandomAuthUserFixture(t *testing.T, ctx context.Context, sqlExec db.SQLExecuter, passwordEncrypter PasswordEncrypter, isAdmin bool, roles ...string) *RandomAuthUser { randomSuffix := randStringRunes(t, 5) + randomSuffix = strings.TrimSpace(strings.ToLower(randomSuffix)) email := fmt.Sprintf("email%s@randomemail.com", randomSuffix) password := "password" + randomSuffix firstName := "firstName" + randomSuffix diff --git a/stellar-auth/pkg/auth/manager.go b/stellar-auth/pkg/auth/manager.go index 439c11454..264c18b3b 100644 --- a/stellar-auth/pkg/auth/manager.go +++ b/stellar-auth/pkg/auth/manager.go @@ -2,6 +2,7 @@ package auth import ( "fmt" + "strings" "time" "github.com/stellar/stellar-disbursement-platform-backend/db" @@ -20,7 +21,11 @@ type User struct { Roles []string `json:"roles"` } -func (u *User) Validate() error { +func (u *User) SanitizeAndValidate() error { + u.Email = strings.TrimSpace(strings.ToLower(u.Email)) + u.FirstName = strings.TrimSpace(u.FirstName) + u.LastName = strings.TrimSpace(u.LastName) + if u.Email == "" { return fmt.Errorf("email is required") } else if err := utils.ValidateEmail(u.Email); err != nil { diff --git a/stellar-auth/pkg/auth/manager_test.go b/stellar-auth/pkg/auth/manager_test.go index 7fad91a98..60e18a410 100644 --- a/stellar-auth/pkg/auth/manager_test.go +++ b/stellar-auth/pkg/auth/manager_test.go @@ -7,26 +7,100 @@ import ( ) func Test_User_Validate(t *testing.T) { - user := &User{ - ID: "", - FirstName: "", - LastName: "", - Email: "", - IsOwner: false, - Roles: []string{}, + testCases := []struct { + name string + user *User + wantEmail string + wantFirst string + wantLast string + }{ + { + name: "trims whitespace and lowercases email", + user: &User{ + Email: " Test@Email.com ", + FirstName: "First", + LastName: "Last", + }, + wantEmail: "test@email.com", + wantFirst: "First", + wantLast: "Last", + }, + { + name: "trims whitespace from names", + user: &User{ + Email: "test@email.com", + FirstName: " First ", + LastName: " Last ", + }, + wantEmail: "test@email.com", + wantFirst: "First", + wantLast: "Last", + }, } - assert.EqualError(t, user.Validate(), "email is required") - - user.Email = "invalid" - assert.EqualError(t, user.Validate(), `email is invalid: the provided email "invalid" is not valid`) - - user.Email = "email@email.com" - assert.EqualError(t, user.Validate(), "first name is required") + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.user.SanitizeAndValidate() + assert.NoError(t, err) + assert.Equal(t, tc.wantEmail, tc.user.Email) + assert.Equal(t, tc.wantFirst, tc.user.FirstName) + assert.Equal(t, tc.wantLast, tc.user.LastName) + }) + } +} - user.FirstName = "First" - assert.EqualError(t, user.Validate(), "last name is required") +func Test_User_SanitizeAndValidate_Validation(t *testing.T) { + testCases := []struct { + name string + user *User + wantErr string + }{ + { + name: "empty email returns error", + user: &User{}, + wantErr: "email is required", + }, + { + name: "invalid email returns error", + user: &User{ + Email: "invalid", + }, + wantErr: `email is invalid: the provided email "invalid" is not valid`, + }, + { + name: "empty first name returns error", + user: &User{ + Email: "test@email.com", + }, + wantErr: "first name is required", + }, + { + name: "empty last name returns error", + user: &User{ + Email: "test@email.com", + FirstName: "First", + }, + wantErr: "last name is required", + }, + { + name: "valid user returns no error", + user: &User{ + Email: "test@email.com", + FirstName: "First", + LastName: "Last", + }, + wantErr: "", + }, + } - user.LastName = "Last" - assert.NoError(t, user.Validate()) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.user.SanitizeAndValidate() + if tc.wantErr != "" { + assert.EqualError(t, err, tc.wantErr) + } else { + assert.NoError(t, err) + } + }) + } } diff --git a/stellar-auth/pkg/cli/add_user.go b/stellar-auth/pkg/cli/add_user.go index 10f08d939..d62340479 100644 --- a/stellar-auth/pkg/cli/add_user.go +++ b/stellar-auth/pkg/cli/add_user.go @@ -197,7 +197,7 @@ func execAddUser(ctx context.Context, dbUrl string, email, firstName, lastName, u, err := authManager.CreateUser(ctx, newUser, password) if err != nil { - return fmt.Errorf("error creating user: %w", err) + return fmt.Errorf("creating user: %w", err) } log.Ctx(ctx).Infof("[CLI - CreateUserAccount] - Created user with account ID %s through CLI with roles %v", u.ID, roles) diff --git a/stellar-auth/pkg/cli/add_user_test.go b/stellar-auth/pkg/cli/add_user_test.go index f87b796a4..39abe7130 100644 --- a/stellar-auth/pkg/cli/add_user_test.go +++ b/stellar-auth/pkg/cli/add_user_test.go @@ -237,22 +237,22 @@ func Test_execAddUserFunc(t *testing.T) { // Invalid invalid err := execAddUser(ctx, dbt.DSN, "", firstName, lastName, password, false, []string{}, tenantID) - assert.EqualError(t, err, "error creating user: error creating user: error validating user fields: email is required") + assert.EqualError(t, err, "creating user: creating user: validating user fields: email is required") err = execAddUser(ctx, dbt.DSN, "wrongemail", firstName, lastName, password, false, []string{}, tenantID) - assert.EqualError(t, err, `error creating user: error creating user: error validating user fields: email is invalid: the provided email "wrongemail" is not valid`) + assert.EqualError(t, err, `creating user: creating user: validating user fields: email is invalid: the provided email "wrongemail" is not valid`) // Invalid password err = execAddUser(ctx, dbt.DSN, email, firstName, lastName, "pass", false, []string{}, tenantID) - assert.EqualError(t, err, fmt.Sprintf("error creating user: error creating user: error encrypting password: password should have at least %d characters", auth.MinPasswordLength)) + assert.EqualError(t, err, fmt.Sprintf("creating user: creating user: encrypting password: password should have at least %d characters", auth.MinPasswordLength)) // Invalid first name err = execAddUser(ctx, dbt.DSN, email, "", lastName, "pass", false, []string{}, tenantID) - assert.EqualError(t, err, "error creating user: error creating user: error validating user fields: first name is required") + assert.EqualError(t, err, "creating user: creating user: validating user fields: first name is required") // Invalid last name err = execAddUser(ctx, dbt.DSN, email, firstName, "", "pass", false, []string{}, tenantID) - assert.EqualError(t, err, "error creating user: error creating user: error validating user fields: last name is required") + assert.EqualError(t, err, "creating user: creating user: validating user fields: last name is required") // Valid user err = execAddUser(ctx, dbt.DSN, email, firstName, lastName, password, false, []string{}, tenantID) @@ -284,7 +284,7 @@ func Test_execAddUserFunc(t *testing.T) { require.NoError(t, err) err = execAddUser(ctx, dbt.DSN, email, firstName, lastName, password, false, []string{}, tenantID) - assert.EqualError(t, err, `error creating user: error creating user: a user with this email already exists`) + assert.EqualError(t, err, `creating user: creating user: a user with this email already exists`) }) t.Run("set the user roles", func(t *testing.T) {