Repository: pennersr/django-allauth

Issue:
login attempts blocked user....unblock
where is the data stored for login attempts made by the users?? How do you clear that? or unblock blocked users?


Discussion:

Resolving patch:
diff --git a/allauth/account/adapter.py b/allauth/account/adapter.py
--- a/allauth/account/adapter.py
+++ b/allauth/account/adapter.py
@@ -470,6 +470,11 @@ def _get_login_attempts_cache_key(self, request, **credentials):
             site_id=site.pk,
             login=login_key)
 
+    def _delete_login_attempts_cached_email(self, request, **credentials):
+        if app_settings.LOGIN_ATTEMPTS_LIMIT:
+            cache_key = self._get_login_attempts_cache_key(request, **credentials)
+            cache.delete(cache_key)
+
     def pre_authenticate(self, request, **credentials):
         if app_settings.LOGIN_ATTEMPTS_LIMIT:
             cache_key = self._get_login_attempts_cache_key(
@@ -495,9 +500,7 @@ def authenticate(self, request, **credentials):
         alt_user = AuthenticationBackend.unstash_authenticated_user()
         user = user or alt_user
         if user and app_settings.LOGIN_ATTEMPTS_LIMIT:
-            cache_key = self._get_login_attempts_cache_key(
-                request, **credentials)
-            cache.delete(cache_key)
+            self._delete_login_attempts_cached_email(request, **credentials)
         else:
             self.authentication_failed(request, **credentials)
         return user
diff --git a/allauth/account/views.py b/allauth/account/views.py
--- a/allauth/account/views.py
+++ b/allauth/account/views.py
@@ -729,7 +729,16 @@ def get_form_kwargs(self):
 
     def form_valid(self, form):
         form.save()
-        get_adapter(self.request).add_message(
+        adapter = get_adapter(self.request)
+
+        if self.reset_user and app_settings.LOGIN_ATTEMPTS_LIMIT:
+            # User successfully reset the password, clear any
+            # possible cache entries for all email addresses.
+            for email in self.reset_user.emailaddress_set.all():
+                adapter._delete_login_attempts_cached_email(
+                    self.request, email=email.email)
+
+        adapter.add_message(
             self.request,
             messages.SUCCESS,
             'account/messages/password_changed.txt')


Test patch:
diff --git a/allauth/account/tests.py b/allauth/account/tests.py
--- a/allauth/account/tests.py
+++ b/allauth/account/tests.py
@@ -536,6 +536,84 @@ def test_login_failed_attempts_exceeded(self):
                 else
                 'The username and/or password you specified are not correct.')
 
+    @override_settings(
+        ACCOUNT_AUTHENTICATION_METHOD=app_settings.AuthenticationMethod.EMAIL,
+        ACCOUNT_LOGIN_ATTEMPTS_LIMIT=1)
+    def test_login_failed_attempts_exceeded_password_reset_attempts_cleared(self):
+        # Ensure that login attempts, once they hit the limit,
+        # can use the password reset mechanism to regain access.
+        user = get_user_model().objects.create(
+            username='john', email="john@example.org", is_active=True)
+        user.set_password('doe')
+        user.save()
+
+        EmailAddress.objects.create(
+            user=user,
+            email='john@example.com',
+            primary=True,
+            verified=True
+        )
+
+        resp = self.client.post(
+            reverse('account_login'),
+            {'login': user.email, 'password': 'bad'})
+        self.assertFormError(
+            resp,
+            'form',
+            None,
+            'The e-mail address and/or password you specified are not correct.')
+
+        resp = self.client.post(
+            reverse('account_login'),
+            {'login': user.email, 'password': 'bad'})
+        self.assertFormError(
+            resp,
+            'form',
+            None,
+            'Too many failed login attempts. Try again later.')
+
+        self.client.post(
+            reverse('account_reset_password'),
+            data={'email': user.email})
+
+        body = mail.outbox[0].body
+        self.assertGreater(body.find('https://'), 0)
+
+        # Extract URL for `password_reset_from_key` view and access it
+        url = body[body.find('/password/reset/'):].split()[0]
+        resp = self.client.get(url)
+        # Follow the redirect the actual password reset page with the key
+        # hidden.
+        url = resp.url
+        resp = self.client.get(url)
+        self.assertTemplateUsed(
+            resp,
+            'account/password_reset_from_key.%s' %
+            app_settings.TEMPLATE_EXTENSION)
+        self.assertFalse('token_fail' in resp.context_data)
+
+        new_password = 'newpass123'
+
+        # Reset the password
+        resp = self.client.post(url,
+                                {'password1': new_password,
+                                 'password2': new_password})
+        self.assertRedirects(resp,
+                             reverse('account_reset_password_from_key_done'))
+
+        # Check the new password is in effect
+        user = get_user_model().objects.get(pk=user.pk)
+        self.assertTrue(user.check_password(new_password))
+
+        resp = self.client.post(
+            reverse('account_login'),
+            {'login': user.email,
+             'password': new_password})
+
+        self.assertRedirects(resp,
+                             settings.LOGIN_REDIRECT_URL,
+                             fetch_redirect_response=False)
+
     def test_login_unverified_account_mandatory(self):
         """Tests login behavior when email verification is mandatory."""
         user = get_user_model().objects.create(username='john')