[MM-59069] Make sure OTP are actual One Time Password (#28074)

Automatic Merge
Этот коммит содержится в:
Julien Tant
2024-09-16 15:44:32 -07:00
коммит произвёл GitHub
родитель d1ecea4c84
Коммит 1909206e16
21 изменённых файлов: 466 добавлений и 59 удалений

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

@@ -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, &notifyProps, &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, &notifyProps, &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, &notifyProps, &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 {