* Isolate more the mfa service

* Simplifing the mfa service

* Removing channels and adding waitgroup

* Migrating mfa service to regular errors

* Fixing tests

* i18n extract

* Addressing PR review comments

* Removing unneeded struct
Этот коммит содержится в:
Jesús Espino
2021-02-18 12:12:21 +01:00
коммит произвёл GitHub
родитель 7585e16d84
Коммит 5f190b5624
7 изменённых файлов: 146 добавлений и 255 удалений

Просмотреть файл

@@ -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 {

Просмотреть файл

@@ -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.

Просмотреть файл

@@ -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."

Просмотреть файл

@@ -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)

Просмотреть файл

@@ -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)

Просмотреть файл

@@ -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

Просмотреть файл

@@ -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))
}
}