Repository: saleor/saleor

Issue:
Inactive customer accounts are able to request and perform password reset
### Steps to reproduce the problem
1. Create a customer through the dashboard or the API
2. Set the account to IsActive = False (**_User account active_** = unchecked)
3. Using the storefront or the API request the password reset using customer's email
4. Customer email should get the reset link (tested with MailHog)
5. Now, the user can use the link to change the password but cannot login because the account is still set as inactive

### What I expected to happen
…
One of the two options:
1) Inactive accounts _cannot_ request a password reset at all
2) If they _can_ request a reset, doing so should set their accounts as active automatically

**System information**
Operating system:
Saleor platform (docker), 2.10
Win 10



Discussion:
IMO we shouldn't send password reset links for inactive accounts. In the mutation that triggers sending password reset links, we should add validation for that. If the account is inactive, a validation error should be returned.
@maarcingebala Yes that is a behavior I would expect as well. I may be able to do a pull request for that in the next two weeks.

Resolving patch:
diff --git a/saleor/account/error_codes.py b/saleor/account/error_codes.py
--- a/saleor/account/error_codes.py
+++ b/saleor/account/error_codes.py
@@ -12,6 +12,7 @@ class AccountErrorCode(Enum):
     DELETE_STAFF_ACCOUNT = "delete_staff_account"
     DELETE_SUPERUSER_ACCOUNT = "delete_superuser_account"
     GRAPHQL_ERROR = "graphql_error"
+    INACTIVE = "inactive"
     INVALID = "invalid"
     INVALID_PASSWORD = "invalid_password"
     LEFT_NOT_MANAGEABLE_PERMISSION = "left_not_manageable_permission"
diff --git a/saleor/graphql/account/mutations/base.py b/saleor/graphql/account/mutations/base.py
--- a/saleor/graphql/account/mutations/base.py
+++ b/saleor/graphql/account/mutations/base.py
@@ -138,6 +138,16 @@ def perform_mutation(cls, _root, info, **data):
                     )
                 }
             )
+
+        if not user.is_active:
+            raise ValidationError(
+                {
+                    "email": ValidationError(
+                        "User with this email is inactive",
+                        code=AccountErrorCode.INACTIVE,
+                    )
+                }
+            )
         send_user_password_reset_email_with_url(redirect_url, user)
         return RequestPasswordReset()
 


Test patch:
diff --git a/saleor/graphql/account/tests/test_account.py b/saleor/graphql/account/tests/test_account.py
--- a/saleor/graphql/account/tests/test_account.py
+++ b/saleor/graphql/account/tests/test_account.py
@@ -3172,6 +3172,27 @@ def test_account_reset_password_invalid_email(
     assert not send_password_reset_email_mock.called
 
 
+@patch("saleor.account.emails._send_password_reset_email")
+def test_account_reset_password_user_is_inactive(
+    send_password_reset_email_mock, user_api_client, customer_user
+):
+    user = customer_user
+    user.is_active = False
+    user.save()
+
+    variables = {
+        "email": customer_user.email,
+        "redirectUrl": "https://www.example.com",
+    }
+    response = user_api_client.post_graphql(REQUEST_PASSWORD_RESET_MUTATION, variables)
+    content = get_graphql_content(response)
+    data = content["data"]["requestPasswordReset"]
+    assert data["errors"] == [
+        {"field": "email", "message": "User with this email is inactive"}
+    ]
+    assert not send_password_reset_email_mock.called
+
+
 @patch("saleor.account.emails._send_password_reset_email")
 def test_account_reset_password_storefront_hosts_not_allowed(
     send_password_reset_email_mock, user_api_client, customer_user