diff --git a/ChangeLog.rst b/ChangeLog.rst index 7aaefd5b24..1e89ec4eb6 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -1,3 +1,22 @@ +0.48.0 (unreleased) +******************* + +Note worthy changes +------------------- + +- Introduced a new setting `ACCOUNT_PREVENT_ENUMERATION` that controls whether + or not information is revealed about whether or not a user account exists. + **Warning**: this is a work in progress, password reset is covered, yet, + signing up is not. + + +Backwards incompatible changes +------------------------------ + +- The newly introduced `ACCOUNT_PREVENT_ENUMERATION` defaults to `True` impacting + the current behavior of the password reset flow. + + 0.47.0 (2021-12-09) ******************* diff --git a/allauth/account/app_settings.py b/allauth/account/app_settings.py index 65aa1f636b..80c75099a5 100644 --- a/allauth/account/app_settings.py +++ b/allauth/account/app_settings.py @@ -46,6 +46,10 @@ def _setting(self, name, dflt): ) return getter(self.prefix + name, dflt) + @property + def PREVENT_ENUMERATION(self): + return self._setting("PREVENT_ENUMERATION", True) + @property def DEFAULT_HTTP_PROTOCOL(self): return self._setting("DEFAULT_HTTP_PROTOCOL", "http").lower() diff --git a/allauth/account/forms.py b/allauth/account/forms.py index f175da4fd0..4a192a0a19 100644 --- a/allauth/account/forms.py +++ b/allauth/account/forms.py @@ -524,18 +524,34 @@ def clean_email(self): email = self.cleaned_data["email"] email = get_adapter().clean_email(email) self.users = filter_users_by_email(email, is_active=True) - if not self.users: + if not self.users and not app_settings.PREVENT_ENUMERATION: raise forms.ValidationError( _("The e-mail address is not assigned to any user account") ) return self.cleaned_data["email"] def save(self, request, **kwargs): - current_site = get_current_site(request) email = self.cleaned_data["email"] + if not self.users: + self._send_unknown_account_mail(request, email) + else: + self._send_password_reset_mail(request, email, self.users, **kwargs) + return email + + def _send_unknown_account_mail(self, request, email): + signup_url = build_absolute_uri(request, reverse("account_signup")) + context = { + "current_site": get_current_site(request), + "email": email, + "request": request, + "signup_url": signup_url, + } + get_adapter(request).send_mail("account/email/unknown_account", email, context) + + def _send_password_reset_mail(self, request, email, users, **kwargs): token_generator = kwargs.get("token_generator", default_token_generator) - for user in self.users: + for user in users: temp_key = token_generator.make_token(user) @@ -551,7 +567,7 @@ def save(self, request, **kwargs): url = build_absolute_uri(request, path) context = { - "current_site": current_site, + "current_site": get_current_site(request), "user": user, "password_reset_url": url, "request": request, @@ -562,7 +578,6 @@ def save(self, request, **kwargs): get_adapter(request).send_mail( "account/email/password_reset_key", email, context ) - return self.cleaned_data["email"] class ResetPasswordKeyForm(PasswordVerificationMixin, forms.Form): diff --git a/allauth/templates/account/email/unknown_account_message.txt b/allauth/templates/account/email/unknown_account_message.txt new file mode 100644 index 0000000000..e4e89d010e --- /dev/null +++ b/allauth/templates/account/email/unknown_account_message.txt @@ -0,0 +1,12 @@ +{% extends "account/email/base_message.txt" %} +{% load i18n %} + +{% block content %}{% autoescape off %}{% blocktrans %}You are receiving this e-mail because you or someone else has requested a +password for your user account. However, we do not have any record of a user +with email {{ email }} in our database. + +This mail can be safely ignored if you did not request a password reset. + +If it was you, you can sign up for an account using the link below.{% endblocktrans %} + +{{ signup_url }}{% endautoescape %}{% endblock %} diff --git a/allauth/templates/account/email/unknown_account_subject.txt b/allauth/templates/account/email/unknown_account_subject.txt new file mode 100644 index 0000000000..6840c40b75 --- /dev/null +++ b/allauth/templates/account/email/unknown_account_subject.txt @@ -0,0 +1,4 @@ +{% load i18n %} +{% autoescape off %} +{% blocktrans %}Password Reset E-mail{% endblocktrans %} +{% endautoescape %} diff --git a/docs/configuration.rst b/docs/configuration.rst index cd07c219b7..ff25e6e2d2 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -166,6 +166,16 @@ ACCOUNT_PRESERVE_USERNAME_CASING (=True) when filter on username. For now, the default is set to ``True`` to maintain backwards compatibility. +ACCOUNT_PREVENT_ENUMERATION (=True) + Controls whether or not information is revealed about whether or not a user + account exists. For example, by entering random email addresses in the + password reset form you can test whether or not those email addresses are + associated with an account. Enabling this setting prevents that, and an email + is always sent, regardless of whether or not the account exists. Note that + there is a slight usability tax to pay because there is no immediate feedback. + **Warning**: this is a work in progress, password reset is covered, yet, + signing up is not. + ACCOUNT_SESSION_REMEMBER (=None) Controls the life time of the session. Set to ``None`` to ask the user ("Remember me?"), ``False`` to not remember, and ``True`` to always