Repository: jazzband/django-axes

Issue:
Still get lockout message even with AXES_LOCK_OUT_AT_FAILURE set to False
I have AXES_LOCK_OUT_AT_FAILURE set to False, which does work properly, since I can still log in using valid credentials even when I have passed the failure limit. However, if I have passed the failure limit and enter invalid credentials, then Axes still shows the lockout message (or redirect/template depending on your settings), even though the account isn't actually locked.

If AXES_LOCK_OUT_AT_FAILURE is set to False, then shouldn't Axes also suppress any lockout responses on failed logins?


Discussion:

Resolving patch:
diff --git a/axes/handlers/cache.py b/axes/handlers/cache.py
--- a/axes/handlers/cache.py
+++ b/axes/handlers/cache.py
@@ -70,7 +70,7 @@ def user_login_failed(
         cache_key = get_client_cache_key(request, credentials)
         self.cache.set(cache_key, failures_since_start, self.cache_timeout)
 
-        if failures_since_start >= settings.AXES_FAILURE_LIMIT:
+        if settings.AXES_LOCK_OUT_AT_FAILURE and failures_since_start >= settings.AXES_FAILURE_LIMIT:
             log.warning('AXES: Locking out %s after repeated login failures.', client_str)
 
             request.axes_locked_out = True
diff --git a/axes/handlers/database.py b/axes/handlers/database.py
--- a/axes/handlers/database.py
+++ b/axes/handlers/database.py
@@ -118,7 +118,7 @@ def user_login_failed(
                 attempt_time=request.axes_attempt_time,
             )
 
-        if failures_since_start >= settings.AXES_FAILURE_LIMIT:
+        if settings.AXES_LOCK_OUT_AT_FAILURE and failures_since_start >= settings.AXES_FAILURE_LIMIT:
             log.warning('AXES: Locking out %s after repeated login failures.', client_str)
 
             request.axes_locked_out = True


Test patch:
diff --git a/axes/tests/base.py b/axes/tests/base.py
--- a/axes/tests/base.py
+++ b/axes/tests/base.py
@@ -154,7 +154,10 @@ def lockout(self):
 
     def check_lockout(self):
         response = self.lockout()
-        self.assertContains(response, self.LOCKED_MESSAGE, status_code=self.BLOCKED)
+        if settings.AXES_LOCK_OUT_AT_FAILURE == True:
+            self.assertContains(response, self.LOCKED_MESSAGE, status_code=self.BLOCKED)
+        else:
+            self.assertNotContains(response, self.LOCKED_MESSAGE, status_code=self.STATUS_SUCCESS)
 
     def cool_off(self):
         sleep(get_cool_off().total_seconds())
diff --git a/axes/tests/test_handlers.py b/axes/tests/test_handlers.py
--- a/axes/tests/test_handlers.py
+++ b/axes/tests/test_handlers.py
@@ -111,6 +111,10 @@ def test_handler(self):
     def test_handler_without_reset(self):
         self.check_handler()
 
+    @override_settings(AXES_LOCK_OUT_AT_FAILURE=False)
+    def test_handler_without_lockout(self):
+        self.check_handler()
+
     @patch('axes.handlers.database.log')
     def test_empty_request(self, log):
         self.check_empty_request(log, 'AxesDatabaseHandler')
@@ -138,6 +142,10 @@ def test_handler(self):
     def test_handler_without_reset(self):
         self.check_handler()
 
+    @override_settings(AXES_LOCK_OUT_AT_FAILURE=False)
+    def test_handler_without_lockout(self):
+        self.check_handler()
+
     @patch('axes.handlers.cache.log')
     def test_empty_request(self, log):
         self.check_empty_request(log, 'AxesCacheHandler')