iMerica / dj-rest-auth

Authentication for Django Rest Framework
https://dj-rest-auth.readthedocs.io/en/latest/index.html
MIT License
1.66k stars 308 forks source link

Allauth ACCOUNT_EMAIL_NOTIFICATIONS not working #602

Open dontic opened 6 months ago

dontic commented 6 months ago

django-allauth introduced ACCOUNT_EMAIL_NOTIFICATIONS this past February:

https://docs.allauth.org/en/latest/release-notes/recent.html

In django-allauth this is handled in their views.

For instance, in PasswordChangeView:

def form_valid(self, form):
    form.save()
    flows.password_change.finalize_password_change(self.request, form.user)
    return super().form_valid(form)
def finalize_password_change(request, user):
    logged_out = logout_on_password_change(request, user)
    adapter = get_adapter(request)
    adapter.add_message(
        request,
        messages.SUCCESS,
        "account/messages/password_changed.txt",
    )
    adapter.send_notification_mail("account/email/password_changed", request.user)
    signals.password_changed.send(
        sender=request.user.__class__,
        request=request,
        user=request.user,
    )
    return logged_out

I believe this is why it's not triggered in dj-rest-auth:

class PasswordChangeSerializer(serializers.Serializer):
    old_password = serializers.CharField(max_length=128)
    new_password1 = serializers.CharField(max_length=128)
    new_password2 = serializers.CharField(max_length=128)

    set_password_form_class = SetPasswordForm

    set_password_form = None

    def __init__(self, *args, **kwargs):
        self.old_password_field_enabled = api_settings.OLD_PASSWORD_FIELD_ENABLED
        self.logout_on_password_change = api_settings.LOGOUT_ON_PASSWORD_CHANGE
        super().__init__(*args, **kwargs)

        if not self.old_password_field_enabled:
            self.fields.pop('old_password')

        self.request = self.context.get('request')
        self.user = getattr(self.request, 'user', None)

    ...

    def save(self):
        self.set_password_form.save()
        if not self.logout_on_password_change:
            from django.contrib.auth import update_session_auth_hash
            update_session_auth_hash(self.request, self.user)

Possible solution:

Add send_notification_mail from django-allauth to the PasswordResetConfirmSerializer and PasswordChangeSerializer views if ACCOUNT_EMAIL_NOTIFICATIONS is True

dontic commented 6 months ago

For whoever finds this and needs a solution right now, the current workaround is to use django's signals.

You won't be able use allauth.account.signals.password_changed(request, user) or allauth.account.signals.password_reset(request, user) because they are not triggered by dj-rest-auth: https://github.com/iMerica/dj-rest-auth/issues/31

Use this function to trigger a password change notification. It will trigger anytime a password is successfully changed, that being either by the password change or password reset endpoints:

@receiver(pre_save, sender=get_user_model())
def detect_password_change(sender, instance, **kwargs):
    """
    Checks if the user changed his password

    Cannot use the password_changed signal because it is not triggered
    when the user changes his password via dj_rest_auth.
    """
    if instance._password is None:
        return

    try:
        user = get_user_model().objects.get(id=instance.id)
    except get_user_model().DoesNotExist:
        return

    print("password changed")

And send an email manually using this.