diff --git a/app/authentication.go b/app/authentication.go index a4bb62627a..b3d3241adb 100644 --- a/app/authentication.go +++ b/app/authentication.go @@ -188,10 +188,13 @@ func (a *App) CheckUserMfa(user *model.User, token string) *model.AppError { return nil } - mfaService := mfa.New(a, a.Srv().Store) - ok, err := mfaService.ValidateToken(user.MfaSecret, token) + if !*a.Config().ServiceSettings.EnableMultifactorAuthentication { + return model.NewAppError("CheckUserMfa", "mfa.mfa_disabled.app_error", nil, "", http.StatusNotImplemented) + } + + ok, err := mfa.New(a.Srv().Store.User()).ValidateToken(user.MfaSecret, token) if err != nil { - return err + return model.NewAppError("CheckUserMfa", "mfa.validate_token.authenticate.app_error", nil, err.Error(), http.StatusBadRequest) } if !ok { diff --git a/app/user.go b/app/user.go index 725a5b4708..6fd8b7c663 100644 --- a/app/user.go +++ b/app/user.go @@ -721,15 +721,18 @@ func (a *App) sanitizeProfiles(users []*model.User, asAdmin bool) []*model.User } func (a *App) GenerateMfaSecret(userID string) (*model.MfaSecret, *model.AppError) { - user, err := a.GetUser(userID) - if err != nil { - return nil, err + user, appErr := a.GetUser(userID) + if appErr != nil { + return nil, appErr } - mfaService := mfa.New(a, a.Srv().Store) - secret, img, err := mfaService.GenerateSecret(user) + if !*a.Config().ServiceSettings.EnableMultifactorAuthentication { + return nil, model.NewAppError("GenerateMfaSecret", "mfa.mfa_disabled.app_error", nil, "", http.StatusNotImplemented) + } + + secret, img, err := mfa.New(a.Srv().Store.User()).GenerateSecret(*a.Config().ServiceSettings.SiteURL, user.Email, user.Id) if err != nil { - return nil, err + return nil, model.NewAppError("GenerateMfaSecret", "mfa.generate_qr_code.create_code.app_error", nil, err.Error(), http.StatusInternalServerError) } // Make sure the old secret is not cached on any cluster nodes. @@ -755,9 +758,17 @@ func (a *App) ActivateMfa(userID, token string) *model.AppError { return model.NewAppError("ActivateMfa", "api.user.activate_mfa.email_and_ldap_only.app_error", nil, "", http.StatusBadRequest) } - mfaService := mfa.New(a, a.Srv().Store) - if err := mfaService.Activate(user, token); err != nil { - return err + if !*a.Config().ServiceSettings.EnableMultifactorAuthentication { + return model.NewAppError("ActivateMfa", "mfa.mfa_disabled.app_error", nil, "", http.StatusNotImplemented) + } + + if err := mfa.New(a.Srv().Store.User()).Activate(user.MfaSecret, user.Id, token); err != nil { + switch { + case errors.Is(err, mfa.InvalidToken): + return model.NewAppError("ActivateMfa", "mfa.activate.bad_token.app_error", nil, "", http.StatusUnauthorized) + default: + return model.NewAppError("ActivateMfa", "mfa.activate.app_error", nil, err.Error(), http.StatusInternalServerError) + } } // Make sure old MFA status is not cached locally or in cluster nodes. @@ -767,9 +778,12 @@ func (a *App) ActivateMfa(userID, token string) *model.AppError { } func (a *App) DeactivateMfa(userID string) *model.AppError { - mfaService := mfa.New(a, a.Srv().Store) - if err := mfaService.Deactivate(userID); err != nil { - return err + if !*a.Config().ServiceSettings.EnableMultifactorAuthentication { + return model.NewAppError("DeactivateMfa", "mfa.mfa_disabled.app_error", nil, "", http.StatusNotImplemented) + } + + if err := mfa.New(a.Srv().Store.User()).Deactivate(userID); err != nil { + return model.NewAppError("DeactivateMfa", "mfa.deactivate.app_error", nil, err.Error(), http.StatusInternalServerError) } // Make sure old MFA status is not cached locally or in cluster nodes. diff --git a/i18n/en.json b/i18n/en.json index 7e854e6851..eb2af52a1d 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -6935,33 +6935,21 @@ "translation": "Mattermost Security Bulletin" }, { - "id": "mfa.activate.authenticate.app_error", - "translation": "Error attempting to authenticate MFA token." + "id": "mfa.activate.app_error", + "translation": "Unable to update MFA active status for the user." }, { "id": "mfa.activate.bad_token.app_error", "translation": "Invalid MFA token." }, { - "id": "mfa.activate.save_active.app_error", + "id": "mfa.deactivate.app_error", "translation": "Unable to update MFA active status for the user." }, - { - "id": "mfa.deactivate.save_active.app_error", - "translation": "Unable to update MFA active status for the user." - }, - { - "id": "mfa.deactivate.save_secret.app_error", - "translation": "Error clearing the MFA secret." - }, { "id": "mfa.generate_qr_code.create_code.app_error", "translation": "Error generating QR code." }, - { - "id": "mfa.generate_qr_code.save_secret.app_error", - "translation": "Error saving the MFA secret." - }, { "id": "mfa.mfa_disabled.app_error", "translation": "Multi-factor authentication has been disabled on this server." diff --git a/model/utils.go b/model/utils.go index 3509bec561..c3761a6822 100644 --- a/model/utils.go +++ b/model/utils.go @@ -185,14 +185,6 @@ func NewRandomString(length int) string { return encoding.EncodeToString(data)[:length] } -// NewRandomBase32String returns a base32 encoded string of a random slice -// of bytes of the given size. The resulting entropy will be (8 * size) bits. -func NewRandomBase32String(size int) string { - data := make([]byte, size) - rand.Read(data) - return base32.StdEncoding.EncodeToString(data) -} - // GetMillis is a convenience method to get milliseconds since epoch. func GetMillis() int64 { return time.Now().UnixNano() / int64(time.Millisecond) diff --git a/model/utils_test.go b/model/utils_test.go index 38ff68d092..9a7c819bf4 100644 --- a/model/utils_test.go +++ b/model/utils_test.go @@ -5,7 +5,6 @@ package model import ( "bytes" - "encoding/base32" "fmt" "net/http" "reflect" @@ -32,13 +31,6 @@ func TestRandomString(t *testing.T) { } } -func TestRandomBase32String(t *testing.T) { - for i := 0; i < 1000; i++ { - str := NewRandomBase32String(i) - require.Len(t, str, base32.StdEncoding.EncodedLen(i)) - } -} - func TestGetMillisForTime(t *testing.T) { thisTimeMillis := int64(1471219200000) thisTime := time.Date(2016, time.August, 15, 0, 0, 0, 0, time.UTC) diff --git a/services/mfa/mfa.go b/services/mfa/mfa.go index 6b87b07ddb..d3a55ff4d5 100644 --- a/services/mfa/mfa.go +++ b/services/mfa/mfa.go @@ -4,39 +4,44 @@ package mfa import ( + "crypto/rand" + "encoding/base32" "fmt" - "net/http" "net/url" "strings" "github.com/dgryski/dgoogauth" "github.com/mattermost/rsc/qr" - - "github.com/mattermost/mattermost-server/v5/model" - "github.com/mattermost/mattermost-server/v5/services/configservice" - "github.com/mattermost/mattermost-server/v5/store" + "github.com/pkg/errors" ) +// InvalidToken indicates the case where the token validation has failed. +var InvalidToken = errors.New("invalid mfa token") + const ( // This will result in 160 bits of entropy (base32 encoded), as recommended by rfc4226. - MFASecretSize = 20 + mfaSecretSize = 20 ) -type Mfa struct { - ConfigService configservice.ConfigService - Store store.Store +type Store interface { + UpdateMfaActive(userId string, active bool) error + UpdateMfaSecret(userId, secret string) error } -func New(configService configservice.ConfigService, store store.Store) Mfa { - return Mfa{configService, store} +type MFA struct { + store Store } -func (m *Mfa) checkConfig() *model.AppError { - if !*m.ConfigService.Config().ServiceSettings.EnableMultifactorAuthentication { - return model.NewAppError("checkConfig", "mfa.mfa_disabled.app_error", nil, "", http.StatusNotImplemented) - } +func New(store Store) *MFA { + return &MFA{store} +} - return nil +// newRandomBase32String returns a base32 encoded string of a random slice +// of bytes of the given size. The resulting entropy will be (8 * size) bits. +func newRandomBase32String(size int) string { + data := make([]byte, size) + rand.Read(data) + return base32.StdEncoding.EncodeToString(data) } func getIssuerFromUrl(uri string) string { @@ -52,39 +57,33 @@ func getIssuerFromUrl(uri string) string { return url.QueryEscape(issuer) } -func (m *Mfa) GenerateSecret(user *model.User) (string, []byte, *model.AppError) { - if err := m.checkConfig(); err != nil { - return "", nil, err - } +// GenerateSecret generates a new user mfa secret and store it with the StoreSecret function provided +func (m *MFA) GenerateSecret(siteURL, userEmail, userID string) (string, []byte, error) { + issuer := getIssuerFromUrl(siteURL) - issuer := getIssuerFromUrl(*m.ConfigService.Config().ServiceSettings.SiteURL) + secret := newRandomBase32String(mfaSecretSize) - secret := model.NewRandomBase32String(MFASecretSize) - - authLink := fmt.Sprintf("otpauth://totp/%s:%s?secret=%s&issuer=%s", issuer, user.Email, secret, issuer) + authLink := fmt.Sprintf("otpauth://totp/%s:%s?secret=%s&issuer=%s", issuer, userEmail, secret, issuer) code, err := qr.Encode(authLink, qr.H) if err != nil { - return "", nil, model.NewAppError("GenerateQrCode", "mfa.generate_qr_code.create_code.app_error", nil, err.Error(), http.StatusInternalServerError) + return "", nil, errors.Wrap(err, "unable to generate qr code") } img := code.PNG() - if err := m.Store.User().UpdateMfaSecret(user.Id, secret); err != nil { - return "", nil, model.NewAppError("GenerateQrCode", "mfa.generate_qr_code.save_secret.app_error", nil, err.Error(), http.StatusInternalServerError) + if err := m.store.UpdateMfaSecret(userID, secret); err != nil { + return "", nil, errors.Wrap(err, "unable to store mfa secret") } return secret, img, nil } -func (m *Mfa) Activate(user *model.User, token string) *model.AppError { - if err := m.checkConfig(); err != nil { - return err - } - +// Activate set the mfa as active and store it with the StoreActive function provided +func (m *MFA) Activate(userMfaSecret, userID string, token string) error { otpConfig := &dgoogauth.OTPConfig{ - Secret: user.MfaSecret, + Secret: userMfaSecret, WindowSize: 3, HotpCounter: 0, } @@ -93,47 +92,35 @@ func (m *Mfa) Activate(user *model.User, token string) *model.AppError { ok, err := otpConfig.Authenticate(trimmedToken) if err != nil { - return model.NewAppError("Activate", "mfa.activate.authenticate.app_error", nil, err.Error(), http.StatusInternalServerError) + return errors.Wrap(err, "unable to parse the token") } if !ok { - return model.NewAppError("Activate", "mfa.activate.bad_token.app_error", nil, "", http.StatusUnauthorized) + return InvalidToken } - if appErr := m.Store.User().UpdateMfaActive(user.Id, true); appErr != nil { - return model.NewAppError("Activate", "mfa.activate.save_active.app_error", nil, appErr.Error(), http.StatusInternalServerError) + if err := m.store.UpdateMfaActive(userID, true); err != nil { + return errors.Wrap(err, "unable to store mfa active") } return nil } -func (m *Mfa) Deactivate(userId string) *model.AppError { - if err := m.checkConfig(); err != nil { - return err +// Deactivate set the mfa as deactive, remove the mfa secret, store it with the StoreActive and StoreSecret functions provided +func (m *MFA) Deactivate(userId string) error { + if err := m.store.UpdateMfaActive(userId, false); err != nil { + return errors.Wrap(err, "unable to store mfa active") } - schan := make(chan error, 1) - go func() { - schan <- m.Store.User().UpdateMfaSecret(userId, "") - close(schan) - }() - - if err := m.Store.User().UpdateMfaActive(userId, false); err != nil { - return model.NewAppError("Deactivate", "mfa.deactivate.save_active.app_error", nil, err.Error(), http.StatusInternalServerError) - } - - if err := <-schan; err != nil { - return model.NewAppError("Deactivate", "mfa.deactivate.save_secret.app_error", nil, err.Error(), http.StatusInternalServerError) + if err := m.store.UpdateMfaSecret(userId, ""); err != nil { + return errors.Wrap(err, "unable to store mfa secret") } return nil } -func (m *Mfa) ValidateToken(secret, token string) (bool, *model.AppError) { - if err := m.checkConfig(); err != nil { - return false, err - } - +// Validate the provide token using the secret provided +func (m *MFA) ValidateToken(secret, token string) (bool, error) { otpConfig := &dgoogauth.OTPConfig{ Secret: secret, WindowSize: 3, @@ -143,7 +130,7 @@ func (m *Mfa) ValidateToken(secret, token string) (bool, *model.AppError) { trimmedToken := strings.TrimSpace(token) ok, err := otpConfig.Authenticate(trimmedToken) if err != nil { - return false, model.NewAppError("ValidateToken", "mfa.validate_token.authenticate.app_error", nil, err.Error(), http.StatusBadRequest) + return false, errors.Wrap(err, "unable to parse the token") } return ok, nil diff --git a/services/mfa/mfa_test.go b/services/mfa/mfa_test.go index 25babc8356..f63eaa0e42 100644 --- a/services/mfa/mfa_test.go +++ b/services/mfa/mfa_test.go @@ -4,6 +4,7 @@ package mfa import ( + "encoding/base32" "errors" "fmt" "net/url" @@ -11,60 +12,36 @@ import ( "time" "github.com/dgryski/dgoogauth" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/mattermost/mattermost-server/v5/model" "github.com/mattermost/mattermost-server/v5/plugin/plugintest/mock" "github.com/mattermost/mattermost-server/v5/store/storetest/mocks" - "github.com/mattermost/mattermost-server/v5/utils/testutils" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestGenerateSecret(t *testing.T) { - user := &model.User{Id: model.NewId(), Roles: "system_user"} - - config := model.Config{} - config.SetDefaults() - config.ServiceSettings.EnableMultifactorAuthentication = model.NewBool(true) - configService := testutils.StaticConfigService{Cfg: &config} - - t.Run("fail on disabled mfa", func(t *testing.T) { - wrongConfig := model.Config{} - wrongConfig.SetDefaults() - wrongConfig.ServiceSettings.EnableMultifactorAuthentication = model.NewBool(false) - wrongConfigService := testutils.StaticConfigService{Cfg: &wrongConfig} - mfa := New(wrongConfigService, nil) - _, _, err := mfa.GenerateSecret(user) - require.NotNil(t, err) - require.Equal(t, "mfa.mfa_disabled.app_error", err.Id) - }) + userID := "user-id" + userEmail := "sample@sample.com" + siteURL := "http://localhost:8065" t.Run("fail on store action fail", func(t *testing.T) { - storeMock := mocks.Store{} - userStoreMock := mocks.UserStore{} - userStoreMock.On("UpdateMfaSecret", user.Id, mock.AnythingOfType("string")).Return(func(userId string, secret string) error { + storeMock := mocks.UserStore{} + storeMock.On("UpdateMfaSecret", userID, mock.AnythingOfType("string")).Return(func(userId string, secret string) error { return errors.New("failed to update mfa secret") }) - storeMock.On("User").Return(&userStoreMock) - mfa := New(configService, &storeMock) - _, _, err := mfa.GenerateSecret(user) - require.NotNil(t, err) - require.Equal(t, "mfa.generate_qr_code.save_secret.app_error", err.Id) + _, _, err := New(&storeMock).GenerateSecret(siteURL, userEmail, userID) + require.Error(t, err) + require.Contains(t, err.Error(), "unable to store mfa secret") }) t.Run("Successful generate secret", func(t *testing.T) { - storeMock := mocks.Store{} - userStoreMock := mocks.UserStore{} - userStoreMock.On("UpdateMfaSecret", user.Id, mock.AnythingOfType("string")).Return(func(userId string, secret string) error { + storeMock := mocks.UserStore{} + storeMock.On("UpdateMfaSecret", userID, mock.AnythingOfType("string")).Return(func(userId string, secret string) error { return nil }) - storeMock.On("User").Return(&userStoreMock) - mfa := New(configService, &storeMock) - - secret, img, err := mfa.GenerateSecret(user) - require.Nil(t, err) + secret, img, err := New(&storeMock).GenerateSecret(siteURL, userEmail, userID) + require.NoError(t, err) assert.Len(t, secret, 32) require.NotEmpty(t, img, "no image set") }) @@ -92,179 +69,117 @@ func TestGetIssuerFromUrl(t *testing.T) { } func TestActivate(t *testing.T) { - user := &model.User{Id: model.NewId(), Roles: "system_user"} - user.MfaSecret = model.NewRandomBase32String(MFASecretSize) + userID := "user-id" + userMfaSecret := newRandomBase32String(mfaSecretSize) - token := dgoogauth.ComputeCode(user.MfaSecret, time.Now().UTC().Unix()/30) - - config := model.Config{} - config.SetDefaults() - config.ServiceSettings.EnableMultifactorAuthentication = model.NewBool(true) - configService := testutils.StaticConfigService{Cfg: &config} - - t.Run("fail on disabled mfa", func(t *testing.T) { - wrongConfig := model.Config{} - wrongConfig.SetDefaults() - wrongConfig.ServiceSettings.EnableMultifactorAuthentication = model.NewBool(false) - wrongConfigService := testutils.StaticConfigService{Cfg: &wrongConfig} - mfa := New(wrongConfigService, nil) - err := mfa.Activate(user, "not-important") - require.NotNil(t, err) - require.Equal(t, "mfa.mfa_disabled.app_error", err.Id) - }) + token := dgoogauth.ComputeCode(userMfaSecret, time.Now().UTC().Unix()/30) t.Run("fail on wrongly formatted token", func(t *testing.T) { - mfa := New(configService, nil) - err := mfa.Activate(user, "invalid-token") - require.NotNil(t, err) - require.Equal(t, "mfa.activate.authenticate.app_error", err.Id) + err := New(nil).Activate(userMfaSecret, userID, "invalid-token") + require.Error(t, err) + require.Contains(t, err.Error(), "unable to parse the token") }) t.Run("fail on invalid token", func(t *testing.T) { - mfa := New(configService, nil) - err := mfa.Activate(user, "000000") - require.NotNil(t, err) - require.Equal(t, "mfa.activate.bad_token.app_error", err.Id) + err := New(nil).Activate(userMfaSecret, userID, "000000") + require.Error(t, err) + require.Contains(t, err.Error(), "invalid mfa token") }) t.Run("fail on store action fail", func(t *testing.T) { - storeMock := mocks.Store{} - userStoreMock := mocks.UserStore{} - userStoreMock.On("UpdateMfaActive", user.Id, true).Return(func(userId string, active bool) error { + storeMock := mocks.UserStore{} + storeMock.On("UpdateMfaActive", userID, true).Return(func(userId string, active bool) error { return errors.New("failed to update mfa active") }) - storeMock.On("User").Return(&userStoreMock) - mfa := New(configService, &storeMock) - err := mfa.Activate(user, fmt.Sprintf("%06d", token)) - require.NotNil(t, err) - require.Equal(t, "mfa.activate.save_active.app_error", err.Id) + err := New(&storeMock).Activate(userMfaSecret, userID, fmt.Sprintf("%06d", token)) + require.Error(t, err) + require.Contains(t, err.Error(), "unable to store mfa active") }) t.Run("Successful activate", func(t *testing.T) { - storeMock := mocks.Store{} - userStoreMock := mocks.UserStore{} - userStoreMock.On("UpdateMfaActive", user.Id, true).Return(func(userId string, active bool) error { + storeMock := mocks.UserStore{} + storeMock.On("UpdateMfaActive", userID, true).Return(func(userId string, active bool) error { return nil }) - storeMock.On("User").Return(&userStoreMock) - mfa := New(configService, &storeMock) - err := mfa.Activate(user, fmt.Sprintf("%06d", token)) - require.Nil(t, err) + err := New(&storeMock).Activate(userMfaSecret, userID, fmt.Sprintf("%06d", token)) + require.NoError(t, err) }) } func TestDeactivate(t *testing.T) { - user := &model.User{Id: model.NewId(), Roles: "system_user"} - - config := model.Config{} - config.SetDefaults() - config.ServiceSettings.EnableMultifactorAuthentication = model.NewBool(true) - configService := testutils.StaticConfigService{Cfg: &config} - - t.Run("fail on disabled mfa", func(t *testing.T) { - wrongConfig := model.Config{} - wrongConfig.SetDefaults() - wrongConfig.ServiceSettings.EnableMultifactorAuthentication = model.NewBool(false) - wrongConfigService := testutils.StaticConfigService{Cfg: &wrongConfig} - mfa := New(wrongConfigService, nil) - err := mfa.Deactivate(user.Id) - require.NotNil(t, err) - require.Equal(t, "mfa.mfa_disabled.app_error", err.Id) - }) + userID := "user-id" t.Run("fail on store UpdateMfaActive action fail", func(t *testing.T) { - storeMock := mocks.Store{} - userStoreMock := mocks.UserStore{} - userStoreMock.On("UpdateMfaActive", user.Id, false).Return(func(userId string, active bool) error { + storeMock := mocks.UserStore{} + storeMock.On("UpdateMfaActive", userID, false).Return(func(userId string, active bool) error { return errors.New("failed to update mfa active") }) - userStoreMock.On("UpdateMfaSecret", user.Id, "").Return(func(userId string, secret string) error { + storeMock.On("UpdateMfaSecret", userID, "").Return(func(userId string, secret string) error { return errors.New("failed to update mfa secret") }) - storeMock.On("User").Return(&userStoreMock) - mfa := New(configService, &storeMock) - err := mfa.Deactivate(user.Id) - require.NotNil(t, err) - require.Equal(t, "mfa.deactivate.save_active.app_error", err.Id) + err := New(&storeMock).Deactivate(userID) + require.Error(t, err) + require.Contains(t, err.Error(), "unable to store mfa active") }) t.Run("fail on store UpdateMfaSecret action fail", func(t *testing.T) { - storeMock := mocks.Store{} - userStoreMock := mocks.UserStore{} - userStoreMock.On("UpdateMfaActive", user.Id, false).Return(func(userId string, active bool) error { + storeMock := mocks.UserStore{} + storeMock.On("UpdateMfaActive", userID, false).Return(func(userId string, active bool) error { return nil }) - userStoreMock.On("UpdateMfaSecret", user.Id, "").Return(func(userId string, secret string) error { + storeMock.On("UpdateMfaSecret", userID, "").Return(func(userId string, secret string) error { return errors.New("failed to update mfa secret") }) - storeMock.On("User").Return(&userStoreMock) - mfa := New(configService, &storeMock) - err := mfa.Deactivate(user.Id) - require.NotNil(t, err) - require.Equal(t, "mfa.deactivate.save_secret.app_error", err.Id) + err := New(&storeMock).Deactivate(userID) + require.Error(t, err) + require.Contains(t, err.Error(), "unable to store mfa secret") }) t.Run("Successful deactivate", func(t *testing.T) { - storeMock := mocks.Store{} - userStoreMock := mocks.UserStore{} - userStoreMock.On("UpdateMfaActive", user.Id, false).Return(func(userId string, active bool) error { + storeMock := mocks.UserStore{} + storeMock.On("UpdateMfaActive", userID, false).Return(func(userId string, active bool) error { return nil }) - userStoreMock.On("UpdateMfaSecret", user.Id, "").Return(func(userId string, secret string) error { + storeMock.On("UpdateMfaSecret", userID, "").Return(func(userId string, secret string) error { return nil }) - storeMock.On("User").Return(&userStoreMock) - mfa := New(configService, &storeMock) - err := mfa.Deactivate(user.Id) - require.Nil(t, err) + err := New(&storeMock).Deactivate(userID) + require.NoError(t, err) }) } func TestValidateToken(t *testing.T) { - secret := model.NewRandomBase32String(MFASecretSize) + secret := newRandomBase32String(mfaSecretSize) token := dgoogauth.ComputeCode(secret, time.Now().UTC().Unix()/30) - config := model.Config{} - config.SetDefaults() - config.ServiceSettings.EnableMultifactorAuthentication = model.NewBool(true) - configService := testutils.StaticConfigService{Cfg: &config} - - t.Run("fail on disabled mfa", func(t *testing.T) { - wrongConfig := model.Config{} - wrongConfig.SetDefaults() - wrongConfig.ServiceSettings.EnableMultifactorAuthentication = model.NewBool(false) - wrongConfigService := testutils.StaticConfigService{Cfg: &wrongConfig} - mfa := New(wrongConfigService, nil) - ok, err := mfa.ValidateToken(secret, fmt.Sprintf("%06d", token)) - require.NotNil(t, err) - require.False(t, ok) - require.Equal(t, "mfa.mfa_disabled.app_error", err.Id) - }) - t.Run("fail on wrongly formatted token", func(t *testing.T) { - mfa := New(configService, nil) - ok, err := mfa.ValidateToken(secret, "invalid-token") - require.NotNil(t, err) + ok, err := New(nil).ValidateToken(secret, "invalid-token") + require.Error(t, err) require.False(t, ok) - require.Equal(t, "mfa.validate_token.authenticate.app_error", err.Id) + require.Contains(t, err.Error(), "unable to parse the token") }) t.Run("fail on invalid token", func(t *testing.T) { - mfa := New(configService, nil) - ok, err := mfa.ValidateToken(secret, "000000") - require.Nil(t, err) + ok, err := New(nil).ValidateToken(secret, "000000") + require.NoError(t, err) require.False(t, ok) }) t.Run("valid token", func(t *testing.T) { - mfa := New(configService, nil) - ok, err := mfa.ValidateToken(secret, fmt.Sprintf("%06d", token)) - require.Nil(t, err) + ok, err := New(nil).ValidateToken(secret, fmt.Sprintf("%06d", token)) + require.NoError(t, err) require.True(t, ok) }) } + +func TestRandomBase32String(t *testing.T) { + for i := 0; i < 1000; i++ { + str := newRandomBase32String(i) + require.Len(t, str, base32.StdEncoding.EncodedLen(i)) + } +}