diff --git a/object/check.go b/object/check.go index 8483cfbf..850f324a 100644 --- a/object/check.go +++ b/object/check.go @@ -18,6 +18,7 @@ import ( "fmt" "regexp" "strings" + "time" "unicode" "github.com/casdoor/casdoor/cred" @@ -30,6 +31,11 @@ var ( reFieldWhiteList *regexp.Regexp ) +const ( + SigninWrongTimesLimit = 5 + LastSignWrongTimeDuration = time.Minute * 15 +) + func init() { reWhiteSpace, _ = regexp.Compile(`\s`) reFieldWhiteList, _ = regexp.Compile(`^[A-Za-z0-9]+$`) @@ -127,7 +133,32 @@ func CheckUserSignup(application *Application, organization *Organization, usern return "" } +func checkSigninErrorTimes(user *User) string { + if user.SigninWrongTimes >= SigninWrongTimesLimit { + lastSignWrongTime, _ := time.Parse(time.RFC3339, user.LastSigninWrongTime) + passedTime := time.Now().UTC().Sub(lastSignWrongTime) + seconds := int(LastSignWrongTimeDuration.Seconds() - passedTime.Seconds()) + + // deny the login if the error times is greater than the limit and the last login time is less than the duration + if seconds > 0 { + return fmt.Sprintf("You have entered the wrong password too many times, please wait for %d minutes %d seconds and try again", seconds/60, seconds%60) + } + + // reset the error times + user.SigninWrongTimes = 0 + + UpdateUser(user.GetId(), user, []string{"signin_wrong_times"}, user.IsGlobalAdmin) + } + + return "" +} + func CheckPassword(user *User, password string) string { + // check the login error times + if msg := checkSigninErrorTimes(user); msg != "" { + return msg + } + organization := GetOrganizationByUser(user) if organization == nil { return "organization does not exist" @@ -137,14 +168,17 @@ func CheckPassword(user *User, password string) string { if credManager != nil { if organization.MasterPassword != "" { if credManager.IsPasswordCorrect(password, organization.MasterPassword, "", organization.PasswordSalt) { + resetUserSigninErrorTimes(user) return "" } } if credManager.IsPasswordCorrect(password, user.Password, user.PasswordSalt, organization.PasswordSalt) { + resetUserSigninErrorTimes(user) return "" } - return "password incorrect" + + return recordSigninErrorInfo(user) } else { return fmt.Sprintf("unsupported password type: %s", organization.PasswordType) } diff --git a/object/check_util.go b/object/check_util.go index 84856f54..3bffa462 100644 --- a/object/check_util.go +++ b/object/check_util.go @@ -14,7 +14,11 @@ package object -import "regexp" +import ( + "fmt" + "regexp" + "time" +) var reRealName *regexp.Regexp @@ -29,3 +33,32 @@ func init() { func isValidRealName(s string) bool { return reRealName.MatchString(s) } + +func resetUserSigninErrorTimes(user *User) { + // if the password is correct and wrong times is not zero, reset the error times + if user.SigninWrongTimes == 0 { + return + } + user.SigninWrongTimes = 0 + UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, user.IsGlobalAdmin) +} + +func recordSigninErrorInfo(user *User) string { + // increase failed login count + user.SigninWrongTimes++ + + if user.SigninWrongTimes >= SigninWrongTimesLimit { + // record the latest failed login time + user.LastSigninWrongTime = time.Now().UTC().Format(time.RFC3339) + } + + // update user + UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, user.IsGlobalAdmin) + leftChances := SigninWrongTimesLimit - user.SigninWrongTimes + if leftChances > 0 { + return fmt.Sprintf("password is incorrect, you have %d remaining chances", leftChances) + } + + // don't show the chance error message if the user has no chance left + return fmt.Sprintf("You have entered the wrong password too many times, please wait for %d minutes and try again", int(LastSignWrongTimeDuration.Minutes())) +} diff --git a/object/user.go b/object/user.go index 1e92c981..6d6e48fe 100644 --- a/object/user.go +++ b/object/user.go @@ -111,6 +111,9 @@ type User struct { Roles []*Role `json:"roles"` Permissions []*Permission `json:"permissions"` + + LastSigninWrongTime string `xorm:"varchar(100)" json:"lastSigninWrongTime"` + SigninWrongTimes int `json:"signinWrongTimes"` } type Userinfo struct { @@ -376,6 +379,7 @@ func UpdateUser(id string, user *User, columns []string, isGlobalAdmin bool) boo "owner", "display_name", "avatar", "location", "address", "region", "language", "affiliation", "title", "homepage", "bio", "score", "tag", "signup_application", "is_admin", "is_global_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", + "signin_wrong_times", "last_signin_wrong_time", } } if isGlobalAdmin {