[MM-59069] Make sure OTP are actual One Time Password (#28074)
Automatic Merge
Этот коммит содержится в:
@@ -212,7 +212,7 @@ func (a *App) CheckUserMfa(rctx request.CTX, user *model.User, token string) *mo
|
||||
return model.NewAppError("CheckUserMfa", "mfa.mfa_disabled.app_error", nil, "", http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
ok, err := mfa.New(a.Srv().Store().User()).ValidateToken(user.MfaSecret, token)
|
||||
ok, err := mfa.New(a.Srv().Store().User()).ValidateToken(user, token)
|
||||
if err != nil {
|
||||
return model.NewAppError("CheckUserMfa", "mfa.validate_token.authenticate.app_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
}
|
||||
|
||||
@@ -249,6 +249,8 @@ channels/db/migrations/mysql/000125_remoteclusters_add_default_team_id.down.sql
|
||||
channels/db/migrations/mysql/000125_remoteclusters_add_default_team_id.up.sql
|
||||
channels/db/migrations/mysql/000126_sharedchannels_remotes_add_deleteat.down.sql
|
||||
channels/db/migrations/mysql/000126_sharedchannels_remotes_add_deleteat.up.sql
|
||||
channels/db/migrations/mysql/000127_add_mfa_used_ts_to_users.down.sql
|
||||
channels/db/migrations/mysql/000127_add_mfa_used_ts_to_users.up.sql
|
||||
channels/db/migrations/postgres/000001_create_teams.down.sql
|
||||
channels/db/migrations/postgres/000001_create_teams.up.sql
|
||||
channels/db/migrations/postgres/000002_create_team_members.down.sql
|
||||
@@ -499,3 +501,5 @@ channels/db/migrations/postgres/000125_remoteclusters_add_default_team_id.down.s
|
||||
channels/db/migrations/postgres/000125_remoteclusters_add_default_team_id.up.sql
|
||||
channels/db/migrations/postgres/000126_sharedchannels_remotes_add_deleteat.down.sql
|
||||
channels/db/migrations/postgres/000126_sharedchannels_remotes_add_deleteat.up.sql
|
||||
channels/db/migrations/postgres/000127_add_mfa_used_ts_to_users.down.sql
|
||||
channels/db/migrations/postgres/000127_add_mfa_used_ts_to_users.up.sql
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE Users DROP COLUMN MfaUsedTimestamps;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE Users ADD COLUMN MfaUsedTimestamps json NULL;
|
||||
@@ -0,0 +1,2 @@
|
||||
|
||||
ALTER TABLE Users DROP COLUMN IF EXISTS MfaUsedTimestamps;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE Users ADD COLUMN IF NOT EXISTS MfaUsedTimestamps jsonb NULL;
|
||||
@@ -11843,6 +11843,24 @@ func (s *OpenTracingLayerUserStore) GetMany(ctx context.Context, ids []string) (
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerUserStore) GetMfaUsedTimestamps(userID string) ([]int, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "UserStore.GetMfaUsedTimestamps")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
result, err := s.UserStore.GetMfaUsedTimestamps(userID)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerUserStore) GetNewUsersForTeam(teamID string, offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "UserStore.GetNewUsersForTeam")
|
||||
@@ -12530,6 +12548,24 @@ func (s *OpenTracingLayerUserStore) SearchWithoutTeam(term string, options *mode
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerUserStore) StoreMfaUsedTimestamps(userID string, ts []int) error {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "UserStore.StoreMfaUsedTimestamps")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
err := s.UserStore.StoreMfaUsedTimestamps(userID, ts)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerUserStore) Update(rctx request.CTX, user *model.User, allowRoleUpdate bool) (*model.UserUpdate, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "UserStore.Update")
|
||||
|
||||
@@ -13529,6 +13529,27 @@ func (s *RetryLayerUserStore) GetMany(ctx context.Context, ids []string) ([]*mod
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerUserStore) GetMfaUsedTimestamps(userID string) ([]int, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.UserStore.GetMfaUsedTimestamps(userID)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerUserStore) GetNewUsersForTeam(teamID string, offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, error) {
|
||||
|
||||
tries := 0
|
||||
@@ -14303,6 +14324,27 @@ func (s *RetryLayerUserStore) SearchWithoutTeam(term string, options *model.User
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerUserStore) StoreMfaUsedTimestamps(userID string, ts []int) error {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
err := s.UserStore.StoreMfaUsedTimestamps(userID, ts)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerUserStore) Update(rctx request.CTX, user *model.User, allowRoleUpdate bool) (*model.UserUpdate, error) {
|
||||
|
||||
tries := 0
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
@@ -55,7 +56,7 @@ func newSqlUserStore(sqlStore *SqlStore, metrics einterfaces.MetricsInterface) s
|
||||
|
||||
// note: we are providing field names explicitly here to maintain order of columns (needed when using raw queries)
|
||||
us.usersQuery = us.getQueryBuilder().
|
||||
Select("u.Id", "u.CreateAt", "u.UpdateAt", "u.DeleteAt", "u.Username", "u.Password", "u.AuthData", "u.AuthService", "u.Email", "u.EmailVerified", "u.Nickname", "u.FirstName", "u.LastName", "u.Position", "u.Roles", "u.AllowMarketing", "u.Props", "u.NotifyProps", "u.LastPasswordUpdate", "u.LastPictureUpdate", "u.FailedAttempts", "u.Locale", "u.Timezone", "u.MfaActive", "u.MfaSecret",
|
||||
Select("u.Id", "u.CreateAt", "u.UpdateAt", "u.DeleteAt", "u.Username", "u.Password", "u.AuthData", "u.AuthService", "u.Email", "u.EmailVerified", "u.Nickname", "u.FirstName", "u.LastName", "u.Position", "u.Roles", "u.AllowMarketing", "u.Props", "u.NotifyProps", "u.LastPasswordUpdate", "u.LastPictureUpdate", "u.FailedAttempts", "u.Locale", "u.Timezone", "u.MfaActive", "u.MfaSecret", "u.MfaUsedTimestamps",
|
||||
"b.UserId IS NOT NULL AS IsBot", "COALESCE(b.Description, '') AS BotDescription", "COALESCE(b.LastIconUpdate, 0) AS BotLastIconUpdate", "u.RemoteId", "u.LastLogin").
|
||||
From("Users u").
|
||||
LeftJoin("Bots b ON ( b.UserId = u.Id )")
|
||||
@@ -85,12 +86,12 @@ func (us SqlUserStore) insert(user *model.User) (sql.Result, error) {
|
||||
(Id, CreateAt, UpdateAt, DeleteAt, Username, Password, AuthData, AuthService,
|
||||
Email, EmailVerified, Nickname, FirstName, LastName, Position, Roles, AllowMarketing,
|
||||
Props, NotifyProps, LastPasswordUpdate, LastPictureUpdate, FailedAttempts,
|
||||
Locale, Timezone, MfaActive, MfaSecret, RemoteId)
|
||||
Locale, Timezone, MfaActive, MfaSecret, RemoteId, MfaUsedTimestamps)
|
||||
VALUES
|
||||
(:Id, :CreateAt, :UpdateAt, :DeleteAt, :Username, :Password, :AuthData, :AuthService,
|
||||
:Email, :EmailVerified, :Nickname, :FirstName, :LastName, :Position, :Roles, :AllowMarketing,
|
||||
:Props, :NotifyProps, :LastPasswordUpdate, :LastPictureUpdate, :FailedAttempts,
|
||||
:Locale, :Timezone, :MfaActive, :MfaSecret, :RemoteId)`
|
||||
:Locale, :Timezone, :MfaActive, :MfaSecret, :RemoteId, :MfaUsedTimestamps)`
|
||||
|
||||
user.Props = wrapBinaryParamStringMap(us.IsBinaryParamEnabled(), user.Props)
|
||||
return us.GetMasterX().NamedExec(query, user)
|
||||
@@ -197,6 +198,7 @@ func (us SqlUserStore) Update(rctx request.CTX, user *model.User, trustedUpdateD
|
||||
user.FailedAttempts = oldUser.FailedAttempts
|
||||
user.MfaSecret = oldUser.MfaSecret
|
||||
user.MfaActive = oldUser.MfaActive
|
||||
user.MfaUsedTimestamps = oldUser.MfaUsedTimestamps
|
||||
user.LastLogin = oldUser.LastLogin
|
||||
|
||||
if !trustedUpdateData {
|
||||
@@ -227,7 +229,7 @@ func (us SqlUserStore) Update(rctx request.CTX, user *model.User, trustedUpdateD
|
||||
AllowMarketing=:AllowMarketing, Props=:Props, NotifyProps=:NotifyProps,
|
||||
LastPasswordUpdate=:LastPasswordUpdate, LastPictureUpdate=:LastPictureUpdate,
|
||||
FailedAttempts=:FailedAttempts,Locale=:Locale, Timezone=:Timezone, MfaActive=:MfaActive,
|
||||
MfaSecret=:MfaSecret, RemoteId=:RemoteId, LastLogin=:LastLogin
|
||||
MfaSecret=:MfaSecret, RemoteId=:RemoteId, LastLogin=:LastLogin, MfaUsedTimestamps=:MfaUsedTimestamps
|
||||
WHERE Id=:Id`
|
||||
|
||||
user.Props = wrapBinaryParamStringMap(us.IsBinaryParamEnabled(), user.Props)
|
||||
@@ -343,7 +345,8 @@ func (us SqlUserStore) UpdateAuthData(userId string, service string, authData *s
|
||||
|
||||
if resetMfa {
|
||||
updateQuery = updateQuery.Set("MfaActive", false).
|
||||
Set("MfaSecret", "")
|
||||
Set("MfaSecret", "").
|
||||
Set("MfaUsedTimestamps", model.StringArray{})
|
||||
}
|
||||
|
||||
queryString, args, err := updateQuery.ToSql()
|
||||
@@ -427,7 +430,7 @@ func (us SqlUserStore) ResetAuthDataToEmailForUsers(service string, userIDs []st
|
||||
func (us SqlUserStore) UpdateMfaSecret(userId, secret string) error {
|
||||
updateAt := model.GetMillis()
|
||||
|
||||
if _, err := us.GetMasterX().Exec("UPDATE Users SET MfaSecret = ?, UpdateAt = ? WHERE Id = ?", secret, updateAt, userId); err != nil {
|
||||
if _, err := us.GetMasterX().Exec("UPDATE Users SET MfaSecret = ?, MfaUsedTimestamps = ?, UpdateAt = ? WHERE Id = ?", secret, model.StringArray{}, updateAt, userId); err != nil {
|
||||
return errors.Wrapf(err, "failed to update User with userId=%s", userId)
|
||||
}
|
||||
|
||||
@@ -444,6 +447,37 @@ func (us SqlUserStore) UpdateMfaActive(userId string, active bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (us SqlUserStore) StoreMfaUsedTimestamps(userId string, ts []int) error {
|
||||
tSStrArray := model.StringArray{}
|
||||
for _, t := range ts {
|
||||
tSStrArray = append(tSStrArray, fmt.Sprintf("%d", t))
|
||||
}
|
||||
|
||||
updateAt := model.GetMillis()
|
||||
if _, err := us.GetMasterX().Exec("UPDATE Users SET MfaUsedTimestamps = ?, UpdateAt = ? WHERE Id = ?", tSStrArray, updateAt, userId); err != nil {
|
||||
return errors.Wrapf(err, "failed to update User with userId=%s", userId)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (us SqlUserStore) GetMfaUsedTimestamps(userId string) ([]int, error) {
|
||||
tsStrArray := model.StringArray{}
|
||||
err := us.GetReplicaX().Get(&tsStrArray, "SELECT MfaUsedTimestamps FROM Users WHERE Id = ?", userId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get MFA used timestamps for user with ID %s", userId)
|
||||
}
|
||||
|
||||
ts := make([]int, len(tsStrArray))
|
||||
for i, t := range tsStrArray {
|
||||
ts[i], err = strconv.Atoi(t)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse MFA used timestamp %s for user with ID %s", t, userId)
|
||||
}
|
||||
}
|
||||
|
||||
return ts, nil
|
||||
}
|
||||
|
||||
// GetMany returns a list of users for the provided list of ids
|
||||
func (us SqlUserStore) GetMany(ctx context.Context, ids []string) ([]*model.User, error) {
|
||||
query := us.usersQuery.Where(sq.Eq{"Id": ids})
|
||||
@@ -474,7 +508,7 @@ func (us SqlUserStore) Get(ctx context.Context, id string) (*model.User, error)
|
||||
&user.Password, &user.AuthData, &user.AuthService, &user.Email, &user.EmailVerified,
|
||||
&user.Nickname, &user.FirstName, &user.LastName, &user.Position, &user.Roles,
|
||||
&user.AllowMarketing, &props, ¬ifyProps, &user.LastPasswordUpdate, &user.LastPictureUpdate,
|
||||
&user.FailedAttempts, &user.Locale, &timezone, &user.MfaActive, &user.MfaSecret,
|
||||
&user.FailedAttempts, &user.Locale, &timezone, &user.MfaActive, &user.MfaSecret, &user.MfaUsedTimestamps,
|
||||
&user.IsBot, &user.BotDescription, &user.BotLastIconUpdate, &user.RemoteId, &user.LastLogin)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
@@ -877,7 +911,7 @@ func (us SqlUserStore) GetAllProfilesInChannel(ctx context.Context, channelID st
|
||||
for rows.Next() {
|
||||
var user model.User
|
||||
var props, notifyProps, timezone []byte
|
||||
if err = rows.Scan(&user.Id, &user.CreateAt, &user.UpdateAt, &user.DeleteAt, &user.Username, &user.Password, &user.AuthData, &user.AuthService, &user.Email, &user.EmailVerified, &user.Nickname, &user.FirstName, &user.LastName, &user.Position, &user.Roles, &user.AllowMarketing, &props, ¬ifyProps, &user.LastPasswordUpdate, &user.LastPictureUpdate, &user.FailedAttempts, &user.Locale, &timezone, &user.MfaActive, &user.MfaSecret, &user.IsBot, &user.BotDescription, &user.BotLastIconUpdate, &user.RemoteId, &user.LastLogin); err != nil {
|
||||
if err = rows.Scan(&user.Id, &user.CreateAt, &user.UpdateAt, &user.DeleteAt, &user.Username, &user.Password, &user.AuthData, &user.AuthService, &user.Email, &user.EmailVerified, &user.Nickname, &user.FirstName, &user.LastName, &user.Position, &user.Roles, &user.AllowMarketing, &props, ¬ifyProps, &user.LastPasswordUpdate, &user.LastPictureUpdate, &user.FailedAttempts, &user.Locale, &timezone, &user.MfaActive, &user.MfaSecret, &user.MfaUsedTimestamps, &user.IsBot, &user.BotDescription, &user.BotLastIconUpdate, &user.RemoteId, &user.LastLogin); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to scan values from rows into User entity")
|
||||
}
|
||||
if err = json.Unmarshal(props, &user.Props); err != nil {
|
||||
|
||||
@@ -417,6 +417,8 @@ type UserStore interface {
|
||||
ResetAuthDataToEmailForUsers(service string, userIDs []string, includeDeleted bool, dryRun bool) (int, error)
|
||||
UpdateMfaSecret(userID, secret string) error
|
||||
UpdateMfaActive(userID string, active bool) error
|
||||
StoreMfaUsedTimestamps(userID string, ts []int) error
|
||||
GetMfaUsedTimestamps(userID string) ([]int, error)
|
||||
Get(ctx context.Context, id string) (*model.User, error)
|
||||
GetMany(ctx context.Context, ids []string) ([]*model.User, error)
|
||||
GetAll() ([]*model.User, error)
|
||||
|
||||
@@ -861,6 +861,36 @@ func (_m *UserStore) GetMany(ctx context.Context, ids []string) ([]*model.User,
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetMfaUsedTimestamps provides a mock function with given fields: userID
|
||||
func (_m *UserStore) GetMfaUsedTimestamps(userID string) ([]int, error) {
|
||||
ret := _m.Called(userID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetMfaUsedTimestamps")
|
||||
}
|
||||
|
||||
var r0 []int
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string) ([]int, error)); ok {
|
||||
return rf(userID)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string) []int); ok {
|
||||
r0 = rf(userID)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]int)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(userID)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetNewUsersForTeam provides a mock function with given fields: teamID, offset, limit, viewRestrictions
|
||||
func (_m *UserStore) GetNewUsersForTeam(teamID string, offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, error) {
|
||||
ret := _m.Called(teamID, offset, limit, viewRestrictions)
|
||||
@@ -1884,6 +1914,24 @@ func (_m *UserStore) SearchWithoutTeam(term string, options *model.UserSearchOpt
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// StoreMfaUsedTimestamps provides a mock function with given fields: userID, ts
|
||||
func (_m *UserStore) StoreMfaUsedTimestamps(userID string, ts []int) error {
|
||||
ret := _m.Called(userID, ts)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for StoreMfaUsedTimestamps")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, []int) error); ok {
|
||||
r0 = rf(userID, ts)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Update provides a mock function with given fields: rctx, user, allowRoleUpdate
|
||||
func (_m *UserStore) Update(rctx request.CTX, user *model.User, allowRoleUpdate bool) (*model.UserUpdate, error) {
|
||||
ret := _m.Called(rctx, user, allowRoleUpdate)
|
||||
|
||||
@@ -101,6 +101,7 @@ func TestUserStore(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
|
||||
t.Run("GetUsersWithInvalidEmails", func(t *testing.T) { testGetUsersWithInvalidEmails(t, rctx, ss) })
|
||||
t.Run("UpdateLastLogin", func(t *testing.T) { testUpdateLastLogin(t, rctx, ss) })
|
||||
t.Run("GetUserReport", func(t *testing.T) { testGetUserReport(t, rctx, ss, s) })
|
||||
t.Run("MfaUsedTimestamps", func(t *testing.T) { testMfaUsedTimestamps(t, rctx, ss) })
|
||||
}
|
||||
|
||||
func testUserStoreSave(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
@@ -6613,3 +6614,24 @@ func testGetUserReport(t *testing.T, rctx request.CTX, ss store.Store, s SqlStor
|
||||
require.Len(t, userReport, 11)
|
||||
})
|
||||
}
|
||||
|
||||
func testMfaUsedTimestamps(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
u1, err := ss.User().Save(rctx, &model.User{
|
||||
Email: "ben@invalid.mattermost.com",
|
||||
Username: "u1" + model.NewId(),
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
defer func() { require.NoError(t, ss.User().PermanentDelete(rctx, u1.Id)) }()
|
||||
|
||||
tss, err := ss.User().GetMfaUsedTimestamps(u1.Id)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, tss)
|
||||
|
||||
err = ss.User().StoreMfaUsedTimestamps(u1.Id, []int{1, 2, 3})
|
||||
require.NoError(t, err)
|
||||
|
||||
tss, err = ss.User().GetMfaUsedTimestamps(u1.Id)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []int{1, 2, 3}, tss)
|
||||
}
|
||||
|
||||
@@ -10661,6 +10661,22 @@ func (s *TimerLayerUserStore) GetMany(ctx context.Context, ids []string) ([]*mod
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerUserStore) GetMfaUsedTimestamps(userID string) ([]int, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.UserStore.GetMfaUsedTimestamps(userID)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("UserStore.GetMfaUsedTimestamps", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerUserStore) GetNewUsersForTeam(teamID string, offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, error) {
|
||||
start := time.Now()
|
||||
|
||||
@@ -11282,6 +11298,22 @@ func (s *TimerLayerUserStore) SearchWithoutTeam(term string, options *model.User
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerUserStore) StoreMfaUsedTimestamps(userID string, ts []int) error {
|
||||
start := time.Now()
|
||||
|
||||
err := s.UserStore.StoreMfaUsedTimestamps(userID, ts)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("UserStore.StoreMfaUsedTimestamps", success, elapsed)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *TimerLayerUserStore) Update(rctx request.CTX, user *model.User, allowRoleUpdate bool) (*model.UserUpdate, error) {
|
||||
start := time.Now()
|
||||
|
||||
|
||||
Ссылка в новой задаче
Block a user