fix: Fix bugs with account locking (#2580)

* fix(security): reset login attempts after successful login

Enforce a maximum number of consecutive failed logins. Successfully logging in should reset the
count.

#2569

* fix(security): fix when user is unlocked

The user should be unlocked when locked_at is set, but the lock has expired.

#2569
This commit is contained in:
Matthew Hill 2023-09-29 19:58:00 -04:00 committed by GitHub
parent 484c60c7ea
commit 4bd7bda60d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 35 additions and 2 deletions

View File

@ -72,7 +72,9 @@ def authenticate_user(session, email: str, password: str) -> PrivateUser | bool:
user_service.lock_user(user) user_service.lock_user(user)
return False return False
return user
user.login_attemps = 0
return db.users.update(user.id, user)
def verify_password(plain_password: str, hashed_password: str) -> bool: def verify_password(plain_password: str, hashed_password: str) -> bool:

View File

@ -23,7 +23,7 @@ class UserService(BaseService):
unlocked = 0 unlocked = 0
for user in locked_users: for user in locked_users:
if force or user.is_locked and user.locked_at is not None: if force or not user.is_locked and user.locked_at is not None:
self.unlock_user(user) self.unlock_user(user)
unlocked += 1 unlocked += 1

View File

@ -61,3 +61,34 @@ def test_lock_unlocker_user(database: AllRepositories, unique_user: TestUser) ->
# Sanity check that the is_locked property is working # Sanity check that the is_locked property is working
user.locked_at = datetime.now() - timedelta(days=2) user.locked_at = datetime.now() - timedelta(days=2)
assert not user.is_locked assert not user.is_locked
def test_reset_locked_users(database: AllRepositories, unique_user: TestUser) -> None:
user_service = UserService(database)
# Test that the user is unlocked
user = database.users.get_one(unique_user.user_id)
assert not user.is_locked
assert not user.locked_at
# Test that the user is locked
user.login_attemps = 5
user = user_service.lock_user(user)
assert user.is_locked
assert user.login_attemps == 5
# Test that the locked user is not unlocked by reset
unlocked = user_service.reset_locked_users()
user = database.users.get_one(unique_user.user_id)
assert unlocked == 0
assert user.is_locked
assert user.login_attemps == 5
# Test that the locked user is unlocked by reset
user.locked_at = datetime.now() - timedelta(days=2)
database.users.update(user.id, user)
unlocked = user_service.reset_locked_users()
user = database.users.get_one(unique_user.user_id)
assert unlocked == 1
assert not user.is_locked
assert user.login_attemps == 0