From f03eed7d6588dcd06620e875041632f8d717f169 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Thu, 15 Aug 2024 08:08:20 -0400 Subject: [PATCH] feat(account): Add DefaultAccountAdapter.send_password_reset_mail This new adapter method can be used to apply custom logic regarding whether a user should be able to initiate the reset password flow. --- allauth/account/adapter.py | 12 +++++++++++- allauth/account/forms.py | 18 ++++++++---------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/allauth/account/adapter.py b/allauth/account/adapter.py index 54291267f3..8c7b3a2516 100644 --- a/allauth/account/adapter.py +++ b/allauth/account/adapter.py @@ -563,9 +563,19 @@ def is_safe_url(self, url): return url_has_allowed_host_and_scheme(url, allowed_hosts=allowed_hosts) + def send_password_reset_mail(self, user, email, context): + """ + Method intended to be overridden in case you need to customize the logic + used to determine whether a user is permitted to request a password reset. + For example, if you are enforcing something like "social only" authentication + in your app, you may want to intervene here by checking `user.has_usable_password` + + """ + return self.send_mail("account/email/password_reset_key", email, context) + def get_reset_password_from_key_url(self, key): """ - Method intented to be overriden in case the password reset email + Method intended to be overridden in case the password reset email needs to be adjusted. """ from allauth.account.internal import flows diff --git a/allauth/account/forms.py b/allauth/account/forms.py index 979f113f17..3a1c43eabf 100644 --- a/allauth/account/forms.py +++ b/allauth/account/forms.py @@ -14,7 +14,7 @@ from allauth.utils import get_username_max_length, set_form_field_order from . import app_settings -from .adapter import get_adapter +from .adapter import DefaultAccountAdapter, get_adapter from .app_settings import AuthenticationMethod from .models import EmailAddress, Login from .utils import ( @@ -598,18 +598,15 @@ def clean_email(self): raise get_adapter().validation_error("unknown_email") return self.cleaned_data["email"] - def save(self, request, **kwargs): + def save(self, request, **kwargs) -> str: email = self.cleaned_data["email"] if not self.users: flows.signup.send_unknown_account_mail(request, email) - else: - self._send_password_reset_mail(request, email, self.users, **kwargs) - return email + return email - def _send_password_reset_mail(self, request, email, users, **kwargs): + adapter: DefaultAccountAdapter = get_adapter() token_generator = kwargs.get("token_generator", default_token_generator) - - for user in users: + for user in self.users: temp_key = token_generator.make_token(user) # send the password reset email @@ -618,7 +615,7 @@ def _send_password_reset_mail(self, request, email, users, **kwargs): # not implementation details such as a separate `uidb36` and # `key. Ideally, this should have done on `urls` level as well. key = f"{uid}-{temp_key}" - url = get_adapter().get_reset_password_from_key_url(key) + url = adapter.get_reset_password_from_key_url(key) context = { "user": user, "password_reset_url": url, @@ -629,7 +626,8 @@ def _send_password_reset_mail(self, request, email, users, **kwargs): if app_settings.AUTHENTICATION_METHOD != AuthenticationMethod.EMAIL: context["username"] = user_username(user) - get_adapter().send_mail("account/email/password_reset_key", email, context) + adapter.send_password_reset_mail(user, email, context) + return email class ResetPasswordKeyForm(PasswordVerificationMixin, forms.Form):