limit the maximum count of password / token check failures per IP/user per minute (#33)

This commit is contained in:
MaysWind
2025-03-09 23:38:53 +08:00
parent a29ff0d553
commit 74844b9a99
23 changed files with 288 additions and 12 deletions
+6
View File
@@ -180,6 +180,12 @@ email_verify_token_expired_time = 3600
# Password reset token expired seconds (60 - 4294967295), default is 3600 (60 minutes) # Password reset token expired seconds (60 - 4294967295), default is 3600 (60 minutes)
password_reset_token_expired_time = 3600 password_reset_token_expired_time = 3600
# Maximum count of password / token check failures (0 - 4294967295) per IP per minute (use the above duplicate checker), default is 5, set to 0 to disable
max_failures_per_ip_per_minute = 5
# Maximum count of password / token check failures (0 - 4294967295) per user per minute (use the above duplicate checker), default is 5, set to 0 to disable
max_failures_per_user_per_minute = 5
# Add X-Request-Id header to response to track user request or error, default is true # Add X-Request-Id header to response to track user request or error, default is true
request_id_header = true request_id_header = true
+3
View File
@@ -28,6 +28,9 @@ var (
container: settings.Container, container: settings.Container,
}, },
ApiUsingDuplicateChecker: ApiUsingDuplicateChecker{ ApiUsingDuplicateChecker: ApiUsingDuplicateChecker{
ApiUsingConfig: ApiUsingConfig{
container: settings.Container,
},
container: duplicatechecker.Container, container: duplicatechecker.Container,
}, },
accounts: services.Accounts, accounts: services.Accounts,
+56 -1
View File
@@ -5,6 +5,7 @@ import (
"github.com/mayswind/ezbookkeeping/pkg/avatars" "github.com/mayswind/ezbookkeeping/pkg/avatars"
"github.com/mayswind/ezbookkeeping/pkg/core" "github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/duplicatechecker"
"github.com/mayswind/ezbookkeeping/pkg/errs" "github.com/mayswind/ezbookkeeping/pkg/errs"
"github.com/mayswind/ezbookkeeping/pkg/log" "github.com/mayswind/ezbookkeeping/pkg/log"
"github.com/mayswind/ezbookkeeping/pkg/models" "github.com/mayswind/ezbookkeeping/pkg/models"
@@ -15,6 +16,7 @@ import (
// AuthorizationsApi represents authorization api // AuthorizationsApi represents authorization api
type AuthorizationsApi struct { type AuthorizationsApi struct {
ApiUsingConfig ApiUsingConfig
ApiUsingDuplicateChecker
ApiWithUserInfo ApiWithUserInfo
users *services.UserService users *services.UserService
tokens *services.TokenService tokens *services.TokenService
@@ -27,6 +29,12 @@ var (
ApiUsingConfig: ApiUsingConfig{ ApiUsingConfig: ApiUsingConfig{
container: settings.Container, container: settings.Container,
}, },
ApiUsingDuplicateChecker: ApiUsingDuplicateChecker{
ApiUsingConfig: ApiUsingConfig{
container: settings.Container,
},
container: duplicatechecker.Container,
},
ApiWithUserInfo: ApiWithUserInfo{ ApiWithUserInfo: ApiWithUserInfo{
ApiUsingConfig: ApiUsingConfig{ ApiUsingConfig: ApiUsingConfig{
container: settings.Container, container: settings.Container,
@@ -51,7 +59,23 @@ func (a *AuthorizationsApi) AuthorizeHandler(c *core.WebContext) (any, *errs.Err
return nil, errs.ErrLoginNameOrPasswordInvalid return nil, errs.ErrLoginNameOrPasswordInvalid
} }
user, err := a.users.GetUserByUsernameOrEmailAndPassword(c, credential.LoginName, credential.Password) err = a.CheckFailureCount(c, 0)
if err != nil {
log.Warnf(c, "[authorizations.AuthorizeHandler] cannot login for user \"%s\", because %s", credential.LoginName, err.Error())
return nil, errs.Or(err, errs.ErrFailureCountLimitReached)
}
user, uid, err := a.users.GetUserByUsernameOrEmailAndPassword(c, credential.LoginName, credential.Password)
if errs.IsCustomError(err) {
failureCheckErr := a.CheckAndIncreaseFailureCount(c, uid)
if failureCheckErr != nil {
log.Warnf(c, "[authorizations.AuthorizeHandler] cannot login for user \"%s\", because %s", credential.LoginName, failureCheckErr.Error())
return nil, errs.Or(failureCheckErr, errs.ErrFailureCountLimitReached)
}
}
if err != nil { if err != nil {
log.Warnf(c, "[authorizations.AuthorizeHandler] login failed for user \"%s\", because %s", credential.LoginName, err.Error()) log.Warnf(c, "[authorizations.AuthorizeHandler] login failed for user \"%s\", because %s", credential.LoginName, err.Error())
@@ -133,6 +157,13 @@ func (a *AuthorizationsApi) TwoFactorAuthorizeHandler(c *core.WebContext) (any,
} }
uid := c.GetCurrentUid() uid := c.GetCurrentUid()
err = a.CheckFailureCount(c, uid)
if err != nil {
log.Warnf(c, "[authorizations.TwoFactorAuthorizeHandler] cannot auth for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.Or(err, errs.ErrFailureCountLimitReached)
}
twoFactorSetting, err := a.twoFactorAuthorizations.GetUserTwoFactorSettingByUid(c, uid) twoFactorSetting, err := a.twoFactorAuthorizations.GetUserTwoFactorSettingByUid(c, uid)
if err != nil { if err != nil {
@@ -142,6 +173,14 @@ func (a *AuthorizationsApi) TwoFactorAuthorizeHandler(c *core.WebContext) (any,
if !totp.Validate(credential.Passcode, twoFactorSetting.Secret) { if !totp.Validate(credential.Passcode, twoFactorSetting.Secret) {
log.Warnf(c, "[authorizations.TwoFactorAuthorizeHandler] passcode is invalid for user \"uid:%d\"", uid) log.Warnf(c, "[authorizations.TwoFactorAuthorizeHandler] passcode is invalid for user \"uid:%d\"", uid)
err = a.CheckAndIncreaseFailureCount(c, uid)
if err != nil {
log.Warnf(c, "[authorizations.TwoFactorAuthorizeHandler] cannot auth for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.Or(err, errs.ErrFailureCountLimitReached)
}
return nil, errs.ErrPasscodeInvalid return nil, errs.ErrPasscodeInvalid
} }
@@ -196,6 +235,13 @@ func (a *AuthorizationsApi) TwoFactorAuthorizeByRecoveryCodeHandler(c *core.WebC
} }
uid := c.GetCurrentUid() uid := c.GetCurrentUid()
err = a.CheckFailureCount(c, uid)
if err != nil {
log.Warnf(c, "[authorizations.TwoFactorAuthorizeByRecoveryCodeHandler] cannot auth for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.Or(err, errs.ErrFailureCountLimitReached)
}
enableTwoFactor, err := a.twoFactorAuthorizations.ExistsTwoFactorSetting(c, uid) enableTwoFactor, err := a.twoFactorAuthorizations.ExistsTwoFactorSetting(c, uid)
if err != nil { if err != nil {
@@ -226,6 +272,15 @@ func (a *AuthorizationsApi) TwoFactorAuthorizeByRecoveryCodeHandler(c *core.WebC
err = a.twoFactorAuthorizations.GetAndUseUserTwoFactorRecoveryCode(c, uid, credential.RecoveryCode, user.Salt) err = a.twoFactorAuthorizations.GetAndUseUserTwoFactorRecoveryCode(c, uid, credential.RecoveryCode, user.Salt)
if errs.IsCustomError(err) {
failureCheckErr := a.CheckAndIncreaseFailureCount(c, uid)
if failureCheckErr != nil {
log.Warnf(c, "[authorizations.TwoFactorAuthorizeByRecoveryCodeHandler] cannot auth for user \"uid:%d\", because %s", uid, failureCheckErr.Error())
return nil, errs.Or(failureCheckErr, errs.ErrFailureCountLimitReached)
}
}
if err != nil { if err != nil {
log.Warnf(c, "[authorizations.TwoFactorAuthorizeByRecoveryCodeHandler] failed to get two-factor recovery code for user \"uid:%d\", because %s", uid, err.Error()) log.Warnf(c, "[authorizations.TwoFactorAuthorizeByRecoveryCodeHandler] failed to get two-factor recovery code for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.Or(err, errs.ErrTwoFactorRecoveryCodeNotExist) return nil, errs.Or(err, errs.ErrTwoFactorRecoveryCodeNotExist)
+66
View File
@@ -5,9 +5,13 @@ import (
"sort" "sort"
"github.com/mayswind/ezbookkeeping/pkg/avatars" "github.com/mayswind/ezbookkeeping/pkg/avatars"
"github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/duplicatechecker" "github.com/mayswind/ezbookkeeping/pkg/duplicatechecker"
"github.com/mayswind/ezbookkeeping/pkg/errs"
"github.com/mayswind/ezbookkeeping/pkg/log"
"github.com/mayswind/ezbookkeeping/pkg/models" "github.com/mayswind/ezbookkeeping/pkg/models"
"github.com/mayswind/ezbookkeeping/pkg/settings" "github.com/mayswind/ezbookkeeping/pkg/settings"
"github.com/mayswind/ezbookkeeping/pkg/utils"
) )
const internalTransactionPictureUrlFormat = "%spictures/%d.%s" const internalTransactionPictureUrlFormat = "%spictures/%d.%s"
@@ -100,6 +104,7 @@ func (a *ApiUsingConfig) GetAfterOpenNotificationContent(userLanguage string, cl
// ApiUsingDuplicateChecker represents an api that need to use duplicate checker // ApiUsingDuplicateChecker represents an api that need to use duplicate checker
type ApiUsingDuplicateChecker struct { type ApiUsingDuplicateChecker struct {
ApiUsingConfig
container *duplicatechecker.DuplicateCheckerContainer container *duplicatechecker.DuplicateCheckerContainer
} }
@@ -113,6 +118,67 @@ func (a *ApiUsingDuplicateChecker) SetSubmissionRemark(checkerType duplicatechec
a.container.SetSubmissionRemark(checkerType, uid, identification, remark) a.container.SetSubmissionRemark(checkerType, uid, identification, remark)
} }
// CheckFailureCount returns whether the failure count of the specified IP and user has reached the limit and increases the failure count
func (a *ApiUsingDuplicateChecker) CheckFailureCount(c *core.WebContext, uid int64) error {
if a.CurrentConfig().MaxFailuresPerIpPerMinute > 0 {
clientIp := c.ClientIP()
ipFailureCount := a.container.GetFailureCount(clientIp)
if ipFailureCount >= a.CurrentConfig().MaxFailuresPerIpPerMinute {
log.Warnf(c, "[base.CheckFailureCount] operation failure via IP \"%s\", current failure count: %d reached the limit", clientIp, ipFailureCount)
return errs.ErrFailureCountLimitReached
}
}
if a.CurrentConfig().MaxFailuresPerUserPerMinute > 0 && uid > 0 {
uidFailureCount := a.container.GetFailureCount(utils.Int64ToString(uid))
if uidFailureCount >= a.CurrentConfig().MaxFailuresPerUserPerMinute {
log.Warnf(c, "[base.CheckFailureCount] operation failure via uid \"%d\", current failure count: %d reached the limit", uid, uidFailureCount)
return errs.ErrFailureCountLimitReached
}
}
return nil
}
// CheckAndIncreaseFailureCount returns whether the failure count of the specified IP and user has reached the limit and increases the failure count
func (a *ApiUsingDuplicateChecker) CheckAndIncreaseFailureCount(c *core.WebContext, uid int64) error {
clientIp := c.ClientIP()
ipFailureCount := uint32(0)
uidFailureCount := uint32(0)
if a.CurrentConfig().MaxFailuresPerIpPerMinute > 0 {
ipFailureCount = a.container.GetFailureCount(clientIp)
}
if a.CurrentConfig().MaxFailuresPerUserPerMinute > 0 && uid > 0 {
uidFailureCount = a.container.GetFailureCount(utils.Int64ToString(uid))
}
if a.CurrentConfig().MaxFailuresPerIpPerMinute > 0 && ipFailureCount < a.CurrentConfig().MaxFailuresPerIpPerMinute {
log.Warnf(c, "[base.CheckAndIncreaseFailureCount] operation failure via IP \"%s\", previous failure count: %d", clientIp, ipFailureCount)
a.container.IncreaseFailureCount(clientIp)
}
if a.CurrentConfig().MaxFailuresPerUserPerMinute > 0 && uid > 0 && uidFailureCount < a.CurrentConfig().MaxFailuresPerUserPerMinute {
log.Warnf(c, "[base.CheckAndIncreaseFailureCount] operation failure via uid \"%d\", previous failure count: %d", uid, uidFailureCount)
a.container.IncreaseFailureCount(utils.Int64ToString(uid))
}
if a.CurrentConfig().MaxFailuresPerIpPerMinute > 0 && ipFailureCount >= a.CurrentConfig().MaxFailuresPerIpPerMinute {
log.Warnf(c, "[base.CheckAndIncreaseFailureCount] operation failure via IP \"%s\", current failure count: %d reached the limit", clientIp, ipFailureCount)
return errs.ErrFailureCountLimitReached
}
if a.CurrentConfig().MaxFailuresPerUserPerMinute > 0 && uid > 0 && uidFailureCount >= a.CurrentConfig().MaxFailuresPerUserPerMinute {
log.Warnf(c, "[base.CheckAndIncreaseFailureCount] operation failure via uid \"%d\", current failure count: %d reached the limit", uid, uidFailureCount)
return errs.ErrFailureCountLimitReached
}
return nil
}
// ApiUsingAvatarProvider represents an api that need to use avatar provider // ApiUsingAvatarProvider represents an api that need to use avatar provider
type ApiUsingAvatarProvider struct { type ApiUsingAvatarProvider struct {
container *avatars.AvatarProviderContainer container *avatars.AvatarProviderContainer
+3
View File
@@ -29,6 +29,9 @@ var (
container: settings.Container, container: settings.Container,
}, },
ApiUsingDuplicateChecker: ApiUsingDuplicateChecker{ ApiUsingDuplicateChecker: ApiUsingDuplicateChecker{
ApiUsingConfig: ApiUsingConfig{
container: settings.Container,
},
container: duplicatechecker.Container, container: duplicatechecker.Container,
}, },
categories: services.TransactionCategories, categories: services.TransactionCategories,
+3
View File
@@ -26,6 +26,9 @@ var (
container: settings.Container, container: settings.Container,
}, },
ApiUsingDuplicateChecker: ApiUsingDuplicateChecker{ ApiUsingDuplicateChecker: ApiUsingDuplicateChecker{
ApiUsingConfig: ApiUsingConfig{
container: settings.Container,
},
container: duplicatechecker.Container, container: duplicatechecker.Container,
}, },
users: services.Users, users: services.Users,
+3
View File
@@ -31,6 +31,9 @@ var (
container: settings.Container, container: settings.Container,
}, },
ApiUsingDuplicateChecker: ApiUsingDuplicateChecker{ ApiUsingDuplicateChecker: ApiUsingDuplicateChecker{
ApiUsingConfig: ApiUsingConfig{
container: settings.Container,
},
container: duplicatechecker.Container, container: duplicatechecker.Container,
}, },
templates: services.TransactionTemplates, templates: services.TransactionTemplates,
+3
View File
@@ -43,6 +43,9 @@ var (
container: settings.Container, container: settings.Container,
}, },
ApiUsingDuplicateChecker: ApiUsingDuplicateChecker{ ApiUsingDuplicateChecker: ApiUsingDuplicateChecker{
ApiUsingConfig: ApiUsingConfig{
container: settings.Container,
},
container: duplicatechecker.Container, container: duplicatechecker.Container,
}, },
transactions: services.Transactions, transactions: services.Transactions,
@@ -8,4 +8,6 @@ type DuplicateChecker interface {
SetSubmissionRemark(checkerType DuplicateCheckerType, uid int64, identification string, remark string) SetSubmissionRemark(checkerType DuplicateCheckerType, uid int64, identification string, remark string)
GetOrSetCronJobRunningInfo(jobName string, runningInfo string, runningInterval time.Duration) (bool, string) GetOrSetCronJobRunningInfo(jobName string, runningInfo string, runningInterval time.Duration) (bool, string)
RemoveCronJobRunningInfo(jobName string) RemoveCronJobRunningInfo(jobName string)
GetFailureCount(failureKey string) uint32
IncreaseFailureCount(failureKey string) uint32
} }
@@ -48,3 +48,13 @@ func (c *DuplicateCheckerContainer) GetOrSetCronJobRunningInfo(jobName string, r
func (c *DuplicateCheckerContainer) RemoveCronJobRunningInfo(jobName string) { func (c *DuplicateCheckerContainer) RemoveCronJobRunningInfo(jobName string) {
c.Current.RemoveCronJobRunningInfo(jobName) c.Current.RemoveCronJobRunningInfo(jobName)
} }
// GetFailureCount returns the failure count of the specified failure key
func (c *DuplicateCheckerContainer) GetFailureCount(failureKey string) uint32 {
return c.Current.GetFailureCount(failureKey)
}
// IncreaseFailureCount increases the failure count of the specified failure key
func (c *DuplicateCheckerContainer) IncreaseFailureCount(failureKey string) uint32 {
return c.Current.IncreaseFailureCount(failureKey)
}
@@ -12,4 +12,5 @@ const (
DUPLICATE_CHECKER_TYPE_NEW_TEMPLATE DuplicateCheckerType = 4 DUPLICATE_CHECKER_TYPE_NEW_TEMPLATE DuplicateCheckerType = 4
DUPLICATE_CHECKER_TYPE_NEW_PICTURE DuplicateCheckerType = 5 DUPLICATE_CHECKER_TYPE_NEW_PICTURE DuplicateCheckerType = 5
DUPLICATE_CHECKER_TYPE_IMPORT_TRANSACTIONS DuplicateCheckerType = 6 DUPLICATE_CHECKER_TYPE_IMPORT_TRANSACTIONS DuplicateCheckerType = 6
DUPLICATE_CHECKER_TYPE_FAILURE_CHECK DuplicateCheckerType = 255
) )
@@ -69,6 +69,34 @@ func (c *InMemoryDuplicateChecker) RemoveCronJobRunningInfo(jobName string) {
c.cache.Delete(c.getCacheKey(DUPLICATE_CHECKER_TYPE_BACKGROUND_CRON_JOB, 0, jobName)) c.cache.Delete(c.getCacheKey(DUPLICATE_CHECKER_TYPE_BACKGROUND_CRON_JOB, 0, jobName))
} }
// GetFailureCount returns the failure count of the specified failure key
func (c *InMemoryDuplicateChecker) GetFailureCount(failureKey string) uint32 {
existedFailureCount, found := c.cache.Get(c.getCacheKey(DUPLICATE_CHECKER_TYPE_FAILURE_CHECK, 0, failureKey))
if found {
return existedFailureCount.(uint32)
}
return 0
}
// IncreaseFailureCount increases the failure count of the specified failure key
func (c *InMemoryDuplicateChecker) IncreaseFailureCount(failureKey string) uint32 {
c.mutex.Lock()
defer c.mutex.Unlock()
cacheKey := c.getCacheKey(DUPLICATE_CHECKER_TYPE_FAILURE_CHECK, 0, failureKey)
_, found := c.cache.Get(cacheKey)
if found {
failureCount, _ := c.cache.IncrementUint32(cacheKey, uint32(1))
return failureCount
} else {
c.cache.Set(cacheKey, uint32(1), 1*time.Minute)
return 1
}
}
func (c *InMemoryDuplicateChecker) getCacheKey(checkerType DuplicateCheckerType, uid int64, identification string) string { func (c *InMemoryDuplicateChecker) getCacheKey(checkerType DuplicateCheckerType, uid int64, identification string) string {
return fmt.Sprintf("%d|%d|%s", checkerType, uid, identification) return fmt.Sprintf("%d|%d|%s", checkerType, uid, identification)
} }
@@ -155,3 +155,77 @@ func TestGetOrSetRunningInfoConcurrent(t *testing.T) {
assert.Equal(t, uint32(999), setRunningInfoCount.Load()) assert.Equal(t, uint32(999), setRunningInfoCount.Load())
} }
func TestGetFailureCount(t *testing.T) {
checker, _ := NewInMemoryDuplicateChecker(&settings.Config{
DuplicateSubmissionsIntervalDuration: time.Second,
InMemoryDuplicateCheckerCleanupIntervalDuration: time.Second,
})
failureKey := "127.0.0.1"
failureCount := checker.GetFailureCount(failureKey)
assert.Equal(t, uint32(0), failureCount)
failureCount = checker.IncreaseFailureCount(failureKey)
assert.Equal(t, uint32(1), failureCount)
failureCount = checker.GetFailureCount(failureKey)
assert.Equal(t, uint32(1), failureCount)
}
func TestIncreaseFailureCount(t *testing.T) {
checker, _ := NewInMemoryDuplicateChecker(&settings.Config{
DuplicateSubmissionsIntervalDuration: time.Second,
InMemoryDuplicateCheckerCleanupIntervalDuration: time.Second,
})
failureKey := "127.0.0.1"
failureCount := checker.IncreaseFailureCount(failureKey)
assert.Equal(t, uint32(1), failureCount)
failureCount = checker.GetFailureCount(failureKey)
assert.Equal(t, uint32(1), failureCount)
failureCount = checker.IncreaseFailureCount(failureKey)
assert.Equal(t, uint32(2), failureCount)
failureCount = checker.GetFailureCount(failureKey)
assert.Equal(t, uint32(2), failureCount)
failureCount = checker.IncreaseFailureCount(failureKey)
assert.Equal(t, uint32(3), failureCount)
failureCount = checker.GetFailureCount(failureKey)
assert.Equal(t, uint32(3), failureCount)
}
func TestIncreaseFailureCountConcurrent(t *testing.T) {
checker, _ := NewInMemoryDuplicateChecker(&settings.Config{
DuplicateSubmissionsIntervalDuration: time.Second,
InMemoryDuplicateCheckerCleanupIntervalDuration: time.Second,
})
failureKey := "127.0.0.1"
concurrentCount := 10
var waitGroup sync.WaitGroup
for routineIndex := 0; routineIndex < concurrentCount; routineIndex++ {
waitGroup.Add(1)
go func(currentRoutineIndex int) {
for cycle := 0; cycle < 10; cycle++ {
checker.IncreaseFailureCount(failureKey)
}
waitGroup.Done()
}(routineIndex)
}
waitGroup.Wait()
failureCount := checker.GetFailureCount(failureKey)
assert.Equal(t, uint32(100), failureCount)
}
+1
View File
@@ -25,6 +25,7 @@ var (
ErrNoFilesUpload = NewNormalError(NormalSubcategoryGlobal, 15, http.StatusBadRequest, "no files uploaded") ErrNoFilesUpload = NewNormalError(NormalSubcategoryGlobal, 15, http.StatusBadRequest, "no files uploaded")
ErrUploadedFileEmpty = NewNormalError(NormalSubcategoryGlobal, 16, http.StatusBadRequest, "uploaded file is empty") ErrUploadedFileEmpty = NewNormalError(NormalSubcategoryGlobal, 16, http.StatusBadRequest, "uploaded file is empty")
ErrExceedMaxUploadFileSize = NewNormalError(NormalSubcategoryGlobal, 17, http.StatusBadRequest, "uploaded file size exceeds the maximum allowed size") ErrExceedMaxUploadFileSize = NewNormalError(NormalSubcategoryGlobal, 17, http.StatusBadRequest, "uploaded file size exceeds the maximum allowed size")
ErrFailureCountLimitReached = NewNormalError(NormalSubcategoryGlobal, 18, http.StatusBadRequest, "failure count exceeded maximum limit")
) )
// GetParameterInvalidMessage returns specific error message for invalid parameter error // GetParameterInvalidMessage returns specific error message for invalid parameter error
+8 -4
View File
@@ -58,7 +58,7 @@ var (
) )
// GetUserByUsernameOrEmailAndPassword returns the user model according to login name and password // GetUserByUsernameOrEmailAndPassword returns the user model according to login name and password
func (s *UserService) GetUserByUsernameOrEmailAndPassword(c core.Context, loginname string, password string) (*models.User, error) { func (s *UserService) GetUserByUsernameOrEmailAndPassword(c core.Context, loginname string, password string) (*models.User, int64, error) {
var user *models.User var user *models.User
var err error var err error
@@ -71,14 +71,18 @@ func (s *UserService) GetUserByUsernameOrEmailAndPassword(c core.Context, loginn
} }
if err != nil { if err != nil {
return nil, err return nil, 0, err
}
if user == nil {
return nil, 0, errs.ErrUserNotFound
} }
if !s.IsPasswordEqualsUserPassword(password, user) { if !s.IsPasswordEqualsUserPassword(password, user) {
return nil, errs.ErrUserPasswordWrong return nil, user.Uid, errs.ErrUserPasswordWrong
} }
return user, nil return user, user.Uid, nil
} }
// GetUserById returns the user model according to user uid // GetUserById returns the user model according to user uid
+7
View File
@@ -144,6 +144,8 @@ const (
defaultTemporaryTokenExpiredTime uint32 = 300 // 5 minutes defaultTemporaryTokenExpiredTime uint32 = 300 // 5 minutes
defaultEmailVerifyTokenExpiredTime uint32 = 3600 // 60 minutes defaultEmailVerifyTokenExpiredTime uint32 = 3600 // 60 minutes
defaultPasswordResetTokenExpiredTime uint32 = 3600 // 60 minutes defaultPasswordResetTokenExpiredTime uint32 = 3600 // 60 minutes
defaultMaxFailuresPerIpPerMinute uint32 = 5
defaultMaxFailuresPerUserPerMinute uint32 = 5
defaultTransactionPictureFileMaxSize uint32 = 10485760 // 10MB defaultTransactionPictureFileMaxSize uint32 = 10485760 // 10MB
defaultUserAvatarFileMaxSize uint32 = 1048576 // 1MB defaultUserAvatarFileMaxSize uint32 = 1048576 // 1MB
@@ -286,6 +288,8 @@ type Config struct {
EmailVerifyTokenExpiredTimeDuration time.Duration EmailVerifyTokenExpiredTimeDuration time.Duration
PasswordResetTokenExpiredTime uint32 PasswordResetTokenExpiredTime uint32
PasswordResetTokenExpiredTimeDuration time.Duration PasswordResetTokenExpiredTimeDuration time.Duration
MaxFailuresPerIpPerMinute uint32
MaxFailuresPerUserPerMinute uint32
EnableRequestIdHeader bool EnableRequestIdHeader bool
// User // User
@@ -768,6 +772,9 @@ func loadSecurityConfiguration(config *Config, configFile *ini.File, sectionName
config.PasswordResetTokenExpiredTimeDuration = time.Duration(config.PasswordResetTokenExpiredTime) * time.Second config.PasswordResetTokenExpiredTimeDuration = time.Duration(config.PasswordResetTokenExpiredTime) * time.Second
config.MaxFailuresPerIpPerMinute = getConfigItemUint32Value(configFile, sectionName, "max_failures_per_ip_per_minute", defaultMaxFailuresPerIpPerMinute)
config.MaxFailuresPerUserPerMinute = getConfigItemUint32Value(configFile, sectionName, "max_failures_per_user_per_minute", defaultMaxFailuresPerUserPerMinute)
config.EnableRequestIdHeader = getConfigItemBoolValue(configFile, sectionName, "request_id_header", true) config.EnableRequestIdHeader = getConfigItemBoolValue(configFile, sectionName, "request_id_header", true)
return nil return nil
+2 -1
View File
@@ -1167,7 +1167,8 @@
"number invalid": "Nummer ist ungültig", "number invalid": "Nummer ist ungültig",
"no files uploaded": "Keine Dateien hochgeladen", "no files uploaded": "Keine Dateien hochgeladen",
"uploaded file is empty": "Hochgeladene Datei ist leer", "uploaded file is empty": "Hochgeladene Datei ist leer",
"uploaded file size exceeds the maximum allowed size": "Hochgeladene Datei überschreitet die maximal zulässige Größe" "uploaded file size exceeds the maximum allowed size": "Hochgeladene Datei überschreitet die maximal zulässige Größe",
"failure count exceeded maximum limit": "Failure count exceeded maximum limit, please try again after some time"
}, },
"parameter": { "parameter": {
"id": "ID", "id": "ID",
+2 -1
View File
@@ -1167,7 +1167,8 @@
"number invalid": "Number is invalid", "number invalid": "Number is invalid",
"no files uploaded": "No files uploaded", "no files uploaded": "No files uploaded",
"uploaded file is empty": "Uploaded file is empty", "uploaded file is empty": "Uploaded file is empty",
"uploaded file size exceeds the maximum allowed size": "Uploaded file size exceeds the maximum allowed size" "uploaded file size exceeds the maximum allowed size": "Uploaded file size exceeds the maximum allowed size",
"failure count exceeded maximum limit": "Failure count exceeded maximum limit, please try again after some time"
}, },
"parameter": { "parameter": {
"id": "ID", "id": "ID",
+2 -1
View File
@@ -1167,7 +1167,8 @@
"number invalid": "El número no es válido", "number invalid": "El número no es válido",
"no files uploaded": "No se subieron archivos", "no files uploaded": "No se subieron archivos",
"uploaded file is empty": "El archivo subido está vacío", "uploaded file is empty": "El archivo subido está vacío",
"uploaded file size exceeds the maximum allowed size": "El tamaño del archivo cargado excede el tamaño máximo permitido" "uploaded file size exceeds the maximum allowed size": "El tamaño del archivo cargado excede el tamaño máximo permitido",
"failure count exceeded maximum limit": "Failure count exceeded maximum limit, please try again after some time"
}, },
"parameter": { "parameter": {
"id": "IDENTIFICACIÓN", "id": "IDENTIFICACIÓN",
+2 -1
View File
@@ -1167,7 +1167,8 @@
"number invalid": "番号が無効です", "number invalid": "番号が無効です",
"no files uploaded": "アップロードされたファイルはありません", "no files uploaded": "アップロードされたファイルはありません",
"uploaded file is empty": "アップロードされたファイルは空です", "uploaded file is empty": "アップロードされたファイルは空です",
"uploaded file size exceeds the maximum allowed size": "アップロードされたファイルが最大許容サイズを超えています" "uploaded file size exceeds the maximum allowed size": "アップロードされたファイルが最大許容サイズを超えています",
"failure count exceeded maximum limit": "Failure count exceeded maximum limit, please try again after some time"
}, },
"parameter": { "parameter": {
"id": "ID", "id": "ID",
+2 -1
View File
@@ -1167,7 +1167,8 @@
"number invalid": "Число недействительно", "number invalid": "Число недействительно",
"no files uploaded": "Файлы не загружены", "no files uploaded": "Файлы не загружены",
"uploaded file is empty": "Загруженный файл пуст", "uploaded file is empty": "Загруженный файл пуст",
"uploaded file size exceeds the maximum allowed size": "Размер загруженного файла превышает максимально допустимый размер" "uploaded file size exceeds the maximum allowed size": "Размер загруженного файла превышает максимально допустимый размер",
"failure count exceeded maximum limit": "Failure count exceeded maximum limit, please try again after some time"
}, },
"parameter": { "parameter": {
"id": "ID", "id": "ID",
+2 -1
View File
@@ -1167,7 +1167,8 @@
"number invalid": "Số không hợp lệ", "number invalid": "Số không hợp lệ",
"no files uploaded": "Không có tệp nào được tải lên", "no files uploaded": "Không có tệp nào được tải lên",
"uploaded file is empty": "Tệp đã tải lên trống", "uploaded file is empty": "Tệp đã tải lên trống",
"uploaded file size exceeds the maximum allowed size": "Kích thước tệp đã tải lên vượt quá kích thước tối đa cho phép" "uploaded file size exceeds the maximum allowed size": "Kích thước tệp đã tải lên vượt quá kích thước tối đa cho phép",
"failure count exceeded maximum limit": "Failure count exceeded maximum limit, please try again after some time"
}, },
"parameter": { "parameter": {
"id": "ID", "id": "ID",
+2 -1
View File
@@ -1167,7 +1167,8 @@
"number invalid": "数字错误", "number invalid": "数字错误",
"no files uploaded": "没有上传文件", "no files uploaded": "没有上传文件",
"uploaded file is empty": "上传的文件为空", "uploaded file is empty": "上传的文件为空",
"uploaded file size exceeds the maximum allowed size": "上传的文件大小超出了允许的最大大小" "uploaded file size exceeds the maximum allowed size": "上传的文件大小超出了允许的最大大小",
"failure count exceeded maximum limit": "失败次数超出最大限制,请稍后重试"
}, },
"parameter": { "parameter": {
"id": "ID", "id": "ID",