mirror of
https://github.com/wgh136/nysoure.git
synced 2025-09-27 20:27:23 +00:00
Implement login attempt tracking and user lockout mechanism
This commit is contained in:
@@ -11,6 +11,8 @@ import (
|
|||||||
"nysoure/server/utils"
|
"nysoure/server/utils"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
@@ -19,6 +21,46 @@ const (
|
|||||||
embedAvatarCount = 1
|
embedAvatarCount = 1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type UserLoginAttempt struct {
|
||||||
|
Attempts uint
|
||||||
|
LockedUntil *time.Time
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
var loginAttempts = sync.Map{}
|
||||||
|
|
||||||
|
func addLoginAttempt(username string) {
|
||||||
|
val, _ := loginAttempts.LoadOrStore(username, &UserLoginAttempt{})
|
||||||
|
attempt := val.(*UserLoginAttempt)
|
||||||
|
attempt.mu.Lock()
|
||||||
|
defer attempt.mu.Unlock()
|
||||||
|
attempt.Attempts++
|
||||||
|
if attempt.Attempts >= 5 {
|
||||||
|
lockDuration := time.Duration(5*(1+attempt.Attempts/5)) * time.Minute
|
||||||
|
until := time.Now().Add(lockDuration)
|
||||||
|
attempt.LockedUntil = &until
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resetLoginAttempts(username string) {
|
||||||
|
loginAttempts.Delete(username)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isUserLocked(username string) bool {
|
||||||
|
attempts, ok := loginAttempts.Load(username)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if attempts.(*UserLoginAttempt).LockedUntil == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if time.Now().After(*attempts.(*UserLoginAttempt).LockedUntil) {
|
||||||
|
loginAttempts.Delete(username)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func CreateUser(username, password, cfToken string) (model.UserViewWithToken, error) {
|
func CreateUser(username, password, cfToken string) (model.UserViewWithToken, error) {
|
||||||
if !config.AllowRegister() {
|
if !config.AllowRegister() {
|
||||||
return model.UserViewWithToken{}, model.NewRequestError("User registration is not allowed")
|
return model.UserViewWithToken{}, model.NewRequestError("User registration is not allowed")
|
||||||
@@ -53,6 +95,9 @@ func CreateUser(username, password, cfToken string) (model.UserViewWithToken, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Login(username, password string) (model.UserViewWithToken, error) {
|
func Login(username, password string) (model.UserViewWithToken, error) {
|
||||||
|
if isUserLocked(username) {
|
||||||
|
return model.UserViewWithToken{}, model.NewRequestError("User is temporarily locked due to too many failed login attempts")
|
||||||
|
}
|
||||||
user, err := dao.GetUserByUsername(username)
|
user, err := dao.GetUserByUsername(username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if model.IsNotFoundError(err) {
|
if model.IsNotFoundError(err) {
|
||||||
@@ -61,8 +106,10 @@ func Login(username, password string) (model.UserViewWithToken, error) {
|
|||||||
return model.UserViewWithToken{}, err
|
return model.UserViewWithToken{}, err
|
||||||
}
|
}
|
||||||
if err := bcrypt.CompareHashAndPassword(user.PasswordHash, []byte(password)); err != nil {
|
if err := bcrypt.CompareHashAndPassword(user.PasswordHash, []byte(password)); err != nil {
|
||||||
|
addLoginAttempt(username)
|
||||||
return model.UserViewWithToken{}, model.NewRequestError("Invalid password")
|
return model.UserViewWithToken{}, model.NewRequestError("Invalid password")
|
||||||
}
|
}
|
||||||
|
resetLoginAttempts(username)
|
||||||
token, err := utils.GenerateToken(user.ID)
|
token, err := utils.GenerateToken(user.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return model.UserViewWithToken{}, err
|
return model.UserViewWithToken{}, err
|
||||||
|
Reference in New Issue
Block a user