diff --git a/ChangeLog.rst b/ChangeLog.rst index 91b64eb9b1..f554416eff 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -10,6 +10,9 @@ Security notice log/print tokens, you will now have to explicitly log the ``token`` field of the ``SocialToken`` instance. +- Enumeration prevention: the behavior on the outside of an actual signup versus + a signup where the user already existed was not fully identical, fixed. + 0.63.3 (2024-05-31) ******************* diff --git a/allauth/account/forms.py b/allauth/account/forms.py index e391a88cb9..3c7415787c 100644 --- a/allauth/account/forms.py +++ b/allauth/account/forms.py @@ -381,10 +381,8 @@ def try_save(self, request): # Don't create a new account, only send an email informing the user # that (s)he already has one... email = self.cleaned_data["email"] - adapter = get_adapter() - adapter.send_account_already_exists_mail(email) + resp = flows.signup.prevent_enumeration(request, email) user = None - resp = adapter.respond_email_verification_sent(request, None) request.session[flows.login.LOGIN_SESSION_KEY] = EmailVerificationStage.key else: user = self.save(request) diff --git a/allauth/account/internal/flows/signup.py b/allauth/account/internal/flows/signup.py index 4396184f47..14fcd4c193 100644 --- a/allauth/account/internal/flows/signup.py +++ b/allauth/account/internal/flows/signup.py @@ -1,3 +1,5 @@ +from django.contrib import messages +from django.http import HttpRequest, HttpResponse from django.urls import reverse from allauth.account.adapter import get_adapter @@ -5,6 +7,19 @@ from allauth.utils import build_absolute_uri +def prevent_enumeration(request: HttpRequest, email: str) -> HttpResponse: + adapter = get_adapter(request) + adapter.send_account_already_exists_mail(email) + adapter.add_message( + request, + messages.INFO, + "account/messages/email_confirmation_sent.txt", + {"email": email, "login": False, "signup": True}, + ) + resp = adapter.respond_email_verification_sent(request, None) + return resp + + def send_unknown_account_mail(request, email): signup_url = get_signup_url(request) context = { diff --git a/allauth/account/tests/test_signup.py b/allauth/account/tests/test_signup.py index 4f4f21fc8d..63b77b86c1 100644 --- a/allauth/account/tests/test_signup.py +++ b/allauth/account/tests/test_signup.py @@ -310,6 +310,7 @@ def test_prevent_enumeration_with_mandatory_verification( assert resp.status_code == 302 assert resp["location"] == reverse("account_email_verification_sent") assertTemplateUsed(resp, "account/email/account_already_exists_message.txt") + assertTemplateUsed(resp, "account/messages/email_confirmation_sent.txt") assert EmailAddress.objects.filter(email="john@example.org").count() == 1 diff --git a/allauth/socialaccount/internal/flows/signup.py b/allauth/socialaccount/internal/flows/signup.py index a5666303a1..c57b73e0bc 100644 --- a/allauth/socialaccount/internal/flows/signup.py +++ b/allauth/socialaccount/internal/flows/signup.py @@ -5,6 +5,7 @@ from allauth import app_settings as allauth_settings from allauth.account import app_settings as account_settings from allauth.account.adapter import get_adapter as get_account_adapter +from allauth.account.internal.flows.signup import prevent_enumeration from allauth.account.utils import ( assess_unique_email, complete_signup, @@ -75,9 +76,7 @@ def process_auto_signup(request, sociallogin): # address. Instead, we're going to send the user an email that # the account already exists, and on the outside make it appear # as if an email verification mail was sent. - account_adapter = get_account_adapter(request) - account_adapter.send_account_already_exists_mail(email) - resp = account_adapter.respond_email_verification_sent(request, None) + resp = prevent_enumeration(request, email) return False, resp elif app_settings.EMAIL_REQUIRED: # Nope, email is required and we don't have it yet...