Repository: saleor/saleor

Issue:
Bug: New account cannot be confirmed if email confirmation is active (invalid token is generated)
### What are you trying to achieve?

Be able to confirm an account created with `accountRegister` by using `confirmAccount` mutation when `ENABLE_ACCOUNT_CONFIRMATION_BY_EMAIL` is enabled.

Introduced in https://github.com/saleor/saleor/pull/13162/files#diff-7f713324b130a27e7c743df32d0a836e06194b627715d947d9d3f8342c9c26dfR137

### Steps to reproduce the problem

1. Enable `ENABLE_ACCOUNT_CONFIRMATION_BY_EMAIL` flag on your instance.
2. Rergister new account
```mutation RegisterAccount($email: String!, $password: String!, 
  $redirectUrl: String, $channel: String) {
  accountRegister(input: {email: $email, password: $password, 
    redirectUrl: $redirectUrl, channel: $channel}) {
    errors {
      field
      message
    }
    requiresConfirmation
  }
}
```
with data like
```{
  "email": "customer@saleor.io",
  "password": "Passw0rd!",
  "redirectUrl": "http://localhost:8000",
  "channel": "default-channel"
}
```
You can modify the code by adding `print` to the line affected (in above section) to be able to copy the token quickly

3. Try to confirm the account
```mutation ConfirmAccount($email: String!, $token: String!){
  confirmAccount(
    email: $email
    token: $token) {
    errors {
      field
      code
      message
    }
    user {
      email
      isActive
    }
  }
}
```
Pasing the mail and token from previous step

4. You get `Invalid or expired token.` error. The issues is that the token is made before the instance is saved, thus creates and passes invalid token that won't work on confirmation.

### What did you expect to happen?

Fix the issue with token being generated before user is saved so that the token is valid. Add proper unit tests.

### Logs

_No response_

### Environment

Saleor version: main
OS and version: n/a



Discussion:

Resolving patch:
diff --git a/saleor/graphql/account/mutations/account/account_register.py b/saleor/graphql/account/mutations/account/account_register.py
--- a/saleor/graphql/account/mutations/account/account_register.py
+++ b/saleor/graphql/account/mutations/account/account_register.py
@@ -146,7 +146,7 @@ def save(cls, info: ResolveInfo, user, cleaned_input):
         )
         manager = get_plugin_manager_promise(info.context).get()
         site = get_site_promise(info.context).get()
-        token = default_token_generator.make_token(user)
+        token = None
         redirect_url = cleaned_input.get("redirect_url")
 
         with traced_atomic_transaction():
@@ -155,6 +155,7 @@ def save(cls, info: ResolveInfo, user, cleaned_input):
                 user.save()
 
                 # Notifications will be deprecated in the future
+                token = default_token_generator.make_token(user)
                 notifications.send_account_confirmation(
                     user,
                     redirect_url,
@@ -166,7 +167,12 @@ def save(cls, info: ResolveInfo, user, cleaned_input):
                 user.save()
 
             if redirect_url:
-                params = urlencode({"email": user.email, "token": token})
+                params = urlencode(
+                    {
+                        "email": user.email,
+                        "token": token or default_token_generator.make_token(user),
+                    }
+                )
                 redirect_url = prepare_url(params, redirect_url)
 
             cls.call_event(


Test patch:
diff --git a/saleor/graphql/account/tests/mutations/account/test_account_register.py b/saleor/graphql/account/tests/mutations/account/test_account_register.py
--- a/saleor/graphql/account/tests/mutations/account/test_account_register.py
+++ b/saleor/graphql/account/tests/mutations/account/test_account_register.py
@@ -1,6 +1,7 @@
 from unittest.mock import patch
 from urllib.parse import urlencode
 
+from django.contrib.auth.tokens import default_token_generator
 from django.test import override_settings
 
 from ......account import events as account_events
@@ -122,6 +123,43 @@ def test_customer_register(
     assert customer_creation_event.user == new_user
 
 
+@override_settings(
+    ENABLE_ACCOUNT_CONFIRMATION_BY_EMAIL=True, ALLOWED_CLIENT_HOSTS=["localhost"]
+)
+@patch("saleor.plugins.manager.PluginsManager.notify")
+def test_customer_register_generates_valid_token(
+    mocked_notify,
+    api_client,
+    channel_PLN,
+    order,
+    site_settings,
+):
+    # given
+    email = "customer@example.com"
+    redirect_url = "http://localhost:3000"
+    variables = {
+        "email": email,
+        "password": "Password",
+        "redirectUrl": redirect_url,
+        "firstName": "saleor",
+        "lastName": "rocks",
+        "languageCode": "PL",
+        "metadata": [{"key": "meta", "value": "data"}],
+        "channel": channel_PLN.slug,
+    }
+
+    # when
+    response = api_client.post_graphql(ACCOUNT_REGISTER_MUTATION, variables)
+    new_user = User.objects.get(email=email)
+    content = get_graphql_content(response)
+    data = content["data"]["accountRegister"]
+
+    # then
+    token = mocked_notify.call_args.kwargs["payload"]["token"]
+    assert not data["errors"]
+    assert default_token_generator.check_token(new_user, token)
+
+
 @patch("saleor.plugins.manager.PluginsManager.notify")
 def test_customer_register_disabled_email_confirmation(
     mocked_notify, api_client, site_settings