diff --git a/allauth/account/adapter.py b/allauth/account/adapter.py index cc51a30cda..9bc26512f1 100644 --- a/allauth/account/adapter.py +++ b/allauth/account/adapter.py @@ -722,6 +722,9 @@ def get_client_ip(self, request): ip = request.META.get("REMOTE_ADDR") return ip + def get_http_user_agent(self, request): + return request.META.get("HTTP_USER_AGENT", "Unspecified") + def generate_emailconfirmation_key(self, email): key = get_random_string(64).lower() return key @@ -758,6 +761,22 @@ def get_reauthentication_methods(self, user): ) return ret + def send_notification_mail(self, template_prefix, user, context): + from allauth.account.models import EmailAddress + + if app_settings.EMAIL_NOTIFICATIONS: + context.update( + { + "site": get_current_site(self.request), + "timestamp": timezone.now(), + "ip": self.get_client_ip(self.request), + "user_agent": self.get_http_user_agent(self.request), + } + ) + email = EmailAddress.objects.get_primary(user) + if email: + self.send_mail(template_prefix, email.email, context) + def get_adapter(request=None): return import_attribute(app_settings.ADAPTER)(request) diff --git a/allauth/account/app_settings.py b/allauth/account/app_settings.py index fe1c2a2156..045d90ccf1 100644 --- a/allauth/account/app_settings.py +++ b/allauth/account/app_settings.py @@ -394,6 +394,10 @@ def EMAIL_UNKNOWN_ACCOUNTS(self): def REAUTHENTICATION_TIMEOUT(self): return self._setting("REAUTHENTICATION_TIMEOUT", 300) + @property + def EMAIL_NOTIFICATIONS(self): + return self._setting("EMAIL_NOTIFICATIONS", False) + @property def REAUTHENTICATION_REQUIRED(self): return self._setting("REAUTHENTICATION_REQUIRED", False) diff --git a/allauth/account/tests/test_confirm_email.py b/allauth/account/tests/test_confirm_email.py index ba295935f7..714434a36d 100644 --- a/allauth/account/tests/test_confirm_email.py +++ b/allauth/account/tests/test_confirm_email.py @@ -380,3 +380,16 @@ def test_confirm_logs_out_user(auth_client, settings, user, user_factory): ) ) assert not auth_client.session.get(SESSION_KEY) + + +def test_notification_on_email_remove(auth_client, user, settings, mailoutbox): + settings.ACCOUNT_EMAIL_NOTIFICATIONS = True + secondary = EmailAddress.objects.create( + email="secondary@email.org", user=user, verified=False, primary=False + ) + resp = auth_client.post( + reverse("account_email"), {"action_remove": "", "email": secondary.email} + ) + assert resp.status_code == 302 + assert len(mailoutbox) == 1 + assert "Following email has been removed" in mailoutbox[0].body diff --git a/allauth/account/tests/test_reset_password.py b/allauth/account/tests/test_reset_password.py index 8a851da218..34548f4093 100644 --- a/allauth/account/tests/test_reset_password.py +++ b/allauth/account/tests/test_reset_password.py @@ -1,4 +1,5 @@ import json +from unittest.mock import patch from django.contrib.auth import get_user_model from django.contrib.auth.models import AnonymousUser @@ -294,3 +295,42 @@ def _create_user_and_login(self, usable_password=True): user = self._create_user(password=password) self.client.force_login(user) return user + + +def test_notification_on_password_change(user_factory, client, settings, mailoutbox): + settings.ACCOUNT_EMAIL_NOTIFICATIONS = True + user = user_factory( + email="john.doe@test.com", + password="password", + email_verified=True, + ) + client.force_login(user) + + client.post( + reverse("account_change_password"), + data={ + "oldpassword": "password", + "password1": "change_password", + "password2": "change_password", + }, + ) + assert len(mailoutbox) == 1 + assert "Your password has been changed" in mailoutbox[0].body + + +def test_notification_on_password_reset(user_factory, client, settings, mailoutbox): + settings.ACCOUNT_EMAIL_NOTIFICATIONS = True + user = user_factory( + email="john.doe@test.com", + password="password", + email_verified=True, + ) + + client.post(reverse("account_reset_password"), data={"email": user.email}) + body = mailoutbox[0].body + url = body[body.find("/password/reset/") :].split()[0] + resp = client.get(url) + resp = client.post(resp.url, {"password1": "newpass123", "password2": "newpass123"}) + + assert len(mailoutbox) == 2 + assert "Your password has been reset" in mailoutbox[1].body diff --git a/allauth/account/views.py b/allauth/account/views.py index f6cfe51808..207911f929 100644 --- a/allauth/account/views.py +++ b/allauth/account/views.py @@ -494,7 +494,8 @@ def get_form_kwargs(self): def form_valid(self, form): email_address = form.save(self.request) - get_adapter(self.request).add_message( + adapter = get_adapter(self.request) + adapter.add_message( self.request, messages.INFO, "account/messages/email_confirmation_sent.txt", @@ -572,6 +573,11 @@ def _action_remove(self, request, *args, **kwargs): "account/messages/email_deleted.txt", {"email": email_address.email}, ) + adapter.send_notification_mail( + "account/email/email_deleted", + request.user, + {"email": email_address.email}, + ) return HttpResponseRedirect(self.get_success_url()) def _action_primary(self, request, *args, **kwargs): @@ -602,7 +608,8 @@ def _action_primary(self, request, *args, **kwargs): except EmailAddress.DoesNotExist: from_email_address = None email_address.set_as_primary() - get_adapter().add_message( + adapter = get_adapter() + adapter.add_message( request, messages.SUCCESS, "account/messages/primary_email_set.txt", @@ -614,6 +621,14 @@ def _action_primary(self, request, *args, **kwargs): from_email_address=from_email_address, to_email_address=email_address, ) + adapter.send_notification_mail( + "account/email/email_changed", + request.user, + { + "from_emailaddress": from_email_address.email, + "to_emailaddress": email_address.email, + }, + ) return HttpResponseRedirect(self.get_success_url()) def get_context_data(self, **kwargs): @@ -691,11 +706,15 @@ def get_success_url(self): def form_valid(self, form): form.save() logout_on_password_change(self.request, form.user) - get_adapter(self.request).add_message( + adapter = get_adapter(self.request) + adapter.add_message( self.request, messages.SUCCESS, "account/messages/password_changed.txt", ) + adapter.send_notification_mail( + "account/email/password_changed", self.request.user, {} + ) signals.password_changed.send( sender=self.request.user.__class__, request=self.request, @@ -746,7 +765,8 @@ def get_success_url(self): def form_valid(self, form): form.save() logout_on_password_change(self.request, form.user) - get_adapter(self.request).add_message( + adapter = get_adapter(self.request) + adapter.add_message( self.request, messages.SUCCESS, "account/messages/password_set.txt" ) signals.password_set.send( @@ -754,6 +774,9 @@ def form_valid(self, form): request=self.request, user=self.request.user, ) + adapter.send_notification_mail( + "account/email/password_set", self.request.user, {} + ) return super().form_valid(form) def get_context_data(self, **kwargs): @@ -905,6 +928,9 @@ def form_valid(self, form): request=self.request, user=self.reset_user, ) + adapter.send_notification_mail( + "account/email/password_reset", self.reset_user, {} + ) if app_settings.LOGIN_ON_PASSWORD_RESET: return perform_login( diff --git a/allauth/mfa/adapter.py b/allauth/mfa/adapter.py index 91e9cf3c0f..3c9709dc9a 100644 --- a/allauth/mfa/adapter.py +++ b/allauth/mfa/adapter.py @@ -1,6 +1,7 @@ from django.utils.translation import gettext_lazy as _ from allauth import app_settings as allauth_settings +from allauth.account.adapter import get_adapter as get_account_adapter from allauth.account.utils import user_email, user_username from allauth.core import context from allauth.mfa import app_settings @@ -68,6 +69,9 @@ def decrypt(self, encrypted_text: str) -> str: def can_delete_authenticator(self, authenticator): return True + def send_notification_mail(self, *args, **kwargs): + return get_account_adapter().send_notification_mail(*args, **kwargs) + def get_adapter(): return import_attribute(app_settings.ADAPTER)() diff --git a/allauth/mfa/tests/test_views.py b/allauth/mfa/tests/test_views.py index 2d8c38a762..0463ac0401 100644 --- a/allauth/mfa/tests/test_views.py +++ b/allauth/mfa/tests/test_views.py @@ -280,3 +280,50 @@ def test_cannot_deactivate_totp(auth_client, user_with_totp, user_password): assert resp.context["form"].errors == { "__all__": [get_adapter().error_messages["cannot_delete_authenticator"]], } + + +def test_notification_on_mfa_activate_totp( + auth_client, reauthentication_bypass, totp_validation_bypass, settings, mailoutbox +): + settings.ACCOUNT_EMAIL_NOTIFICATIONS = True + with reauthentication_bypass(): + resp = auth_client.get(reverse("mfa_activate_totp")) + with totp_validation_bypass(): + resp = auth_client.post( + reverse("mfa_activate_totp"), + { + "code": "123", + }, + ) + assert len(mailoutbox) == 1 + assert "Authenticator App Activated" in mailoutbox[0].subject + assert "Authenticator App has been activated." in mailoutbox[0].body + + +def test_notification_on_mfa_deactivate_totp( + auth_client, user_with_totp, user_password, settings, mailoutbox +): + settings.ACCOUNT_EMAIL_NOTIFICATIONS = True + resp = auth_client.get(reverse("mfa_deactivate_totp")) + assert resp.status_code == 302 + assert resp["location"].startswith(reverse("account_reauthenticate")) + resp = auth_client.post(resp["location"], {"password": user_password}) + assert resp.status_code == 302 + resp = auth_client.post(reverse("mfa_deactivate_totp")) + assert len(mailoutbox) == 1 + assert "Authenticator App deactivated" in mailoutbox[0].subject + assert "Authenticator App has been deactivated." in mailoutbox[0].body + + +def test_notification_on_authenticator_reset( + auth_client, user_with_recovery_codes, user_password, settings, mailoutbox +): + settings.ACCOUNT_EMAIL_NOTIFICATIONS = True + resp = auth_client.get(reverse("mfa_generate_recovery_codes")) + assert resp["location"].startswith(reverse("account_reauthenticate")) + resp = auth_client.post(resp["location"], {"password": user_password}) + assert resp.status_code == 302 + resp = auth_client.post(resp["location"]) + assert len(mailoutbox) == 1 + assert "Recovery codes generated" in mailoutbox[0].subject + assert "Recovery codes has been generated" in mailoutbox[0].body diff --git a/allauth/mfa/views.py b/allauth/mfa/views.py index 62a42b64d5..d02370fc40 100644 --- a/allauth/mfa/views.py +++ b/allauth/mfa/views.py @@ -153,6 +153,9 @@ def form_valid(self, form): adapter.add_message( self.request, messages.SUCCESS, "mfa/messages/totp_activated.txt" ) + adapter.send_notification_mail( + "mfa/email/totp_activated", self.request.user, {} + ) return super().form_valid(form) @@ -212,6 +215,9 @@ def form_valid(self, form): adapter.add_message( self.request, messages.SUCCESS, "mfa/messages/totp_deactivated.txt" ) + adapter.send_notification_mail( + "mfa/email/totp_deactivated", self.request.user, {} + ) return super().form_valid(form) @@ -236,6 +242,9 @@ def form_valid(self, form): signals.authenticator_reset.send( sender=Authenticator, user=self.request.user, authenticator=rc_auth.instance ) + adapter.send_notification_mail( + "mfa/email/recovery_codes_generated", self.request.user, {} + ) return super().form_valid(form) def get_context_data(self, **kwargs): diff --git a/allauth/socialaccount/adapter.py b/allauth/socialaccount/adapter.py index a00c6ecc37..ced89af6f9 100644 --- a/allauth/socialaccount/adapter.py +++ b/allauth/socialaccount/adapter.py @@ -356,6 +356,9 @@ def can_authenticate_by_email(self, login, email): ) return ret + def send_notification_mail(self, *args, **kwargs): + return get_account_adapter().send_notification_mail(*args, **kwargs) + def get_adapter(request=None): return import_attribute(app_settings.ADAPTER)(request) diff --git a/allauth/socialaccount/forms.py b/allauth/socialaccount/forms.py index dc810f3417..bc848ec440 100644 --- a/allauth/socialaccount/forms.py +++ b/allauth/socialaccount/forms.py @@ -63,3 +63,6 @@ def save(self): signals.social_account_removed.send( sender=SocialAccount, request=self.request, socialaccount=account ) + get_adapter().send_notification_mail( + "socialaccount/email/account_disconnected", self.request.user, {} + ) diff --git a/allauth/socialaccount/models.py b/allauth/socialaccount/models.py index 92aecdcfa0..b867aa7661 100644 --- a/allauth/socialaccount/models.py +++ b/allauth/socialaccount/models.py @@ -214,6 +214,10 @@ def connect(self, request, user): sender=SocialLogin, request=request, sociallogin=self ) + get_adapter().send_notification_mail( + "socialaccount/email/account_added", self.user, {} + ) + def serialize(self): serialize_instance = get_adapter().serialize_instance ret = dict( diff --git a/allauth/templates/account/email/base_notification.txt b/allauth/templates/account/email/base_notification.txt new file mode 100644 index 0000000000..d5a6832e07 --- /dev/null +++ b/allauth/templates/account/email/base_notification.txt @@ -0,0 +1,16 @@ +{% extends "account/email/base_message.txt" %} +{% load account %} +{% load i18n %} + +{% block content %}{% autoescape off %}{% user_display user as user_display %}{% blocktrans with site_name=current_site.name %}You are receiving this mail because the following change was made to your account:{% endblocktrans %} + +{% block notification_message %} +{% endblock notification_message%} + +{% blocktrans with site_name=current_site.name %} +Change details: +- When: {{timestamp}} +- From what IP address : {{ip}} +- The browser that was used : {{user_agent}} + +If this change was not made by you, please contact us immediately at support of {{site_name}}.{% endblocktrans %}{% endautoescape %}{% endblock %} diff --git a/allauth/templates/account/email/email_changed_message.txt b/allauth/templates/account/email/email_changed_message.txt new file mode 100644 index 0000000000..dc68e1989f --- /dev/null +++ b/allauth/templates/account/email/email_changed_message.txt @@ -0,0 +1,4 @@ +{% extends "account/email/base_notification.txt" %} +{% load i18n %} + +{% block notification_message %}{% blocktrans %}Your email has been changed.{% endblocktrans %}{% endblock notification_message %} diff --git a/allauth/templates/account/email/email_changed_subject.txt b/allauth/templates/account/email/email_changed_subject.txt new file mode 100644 index 0000000000..cb0702cdb0 --- /dev/null +++ b/allauth/templates/account/email/email_changed_subject.txt @@ -0,0 +1,4 @@ +{% load i18n %} +{% autoescape off %} +{% blocktrans %}Email Changed{% endblocktrans %} +{% endautoescape %} diff --git a/allauth/templates/account/email/email_confirm_message.txt b/allauth/templates/account/email/email_confirm_message.txt new file mode 100644 index 0000000000..23e3054d81 --- /dev/null +++ b/allauth/templates/account/email/email_confirm_message.txt @@ -0,0 +1,4 @@ +{% extends "account/email/base_notification.txt" %} +{% load i18n %} + +{% block notification_message %}{% blocktrans %}Your email has been confirmed.{% endblocktrans %}{% endblock notification_message %} diff --git a/allauth/templates/account/email/email_confirm_subject.txt b/allauth/templates/account/email/email_confirm_subject.txt new file mode 100644 index 0000000000..fe8cf74b8e --- /dev/null +++ b/allauth/templates/account/email/email_confirm_subject.txt @@ -0,0 +1,4 @@ +{% load i18n %} +{% autoescape off %} +{% blocktrans %}Email Confirmation{% endblocktrans %} +{% endautoescape %} diff --git a/allauth/templates/account/email/email_deleted_message.txt b/allauth/templates/account/email/email_deleted_message.txt new file mode 100644 index 0000000000..8ddfc2135a --- /dev/null +++ b/allauth/templates/account/email/email_deleted_message.txt @@ -0,0 +1,4 @@ +{% extends "account/email/base_notification.txt" %} +{% load i18n %} + +{% block notification_message %}{% blocktrans %}Following email has been removed {{email}}.{% endblocktrans %}{% endblock notification_message %} diff --git a/allauth/templates/account/email/email_deleted_subject.txt b/allauth/templates/account/email/email_deleted_subject.txt new file mode 100644 index 0000000000..e923fc8550 --- /dev/null +++ b/allauth/templates/account/email/email_deleted_subject.txt @@ -0,0 +1,4 @@ +{% load i18n %} +{% autoescape off %} +{% blocktrans %}Email Removed{% endblocktrans %} +{% endautoescape %} diff --git a/allauth/templates/account/email/password_changed_message.txt b/allauth/templates/account/email/password_changed_message.txt new file mode 100644 index 0000000000..6a6698bf89 --- /dev/null +++ b/allauth/templates/account/email/password_changed_message.txt @@ -0,0 +1,4 @@ +{% extends "account/email/base_notification.txt" %} +{% load i18n %} + +{% block notification_message %}{% blocktrans %}Your password has been changed.{% endblocktrans %}{% endblock notification_message %} diff --git a/allauth/templates/account/email/password_changed_subject.txt b/allauth/templates/account/email/password_changed_subject.txt new file mode 100644 index 0000000000..b8eecbb566 --- /dev/null +++ b/allauth/templates/account/email/password_changed_subject.txt @@ -0,0 +1,4 @@ +{% load i18n %} +{% autoescape off %} +{% blocktrans %}Password Changed{% endblocktrans %} +{% endautoescape %} diff --git a/allauth/templates/account/email/password_reset_message.txt b/allauth/templates/account/email/password_reset_message.txt new file mode 100644 index 0000000000..82d1013053 --- /dev/null +++ b/allauth/templates/account/email/password_reset_message.txt @@ -0,0 +1,4 @@ +{% extends "account/email/base_notification.txt" %} +{% load i18n %} + +{% block notification_message %}{% blocktrans %}Your password has been reset.{% endblocktrans %}{% endblock notification_message %} diff --git a/allauth/templates/account/email/password_reset_subject.txt b/allauth/templates/account/email/password_reset_subject.txt new file mode 100644 index 0000000000..42201c4482 --- /dev/null +++ b/allauth/templates/account/email/password_reset_subject.txt @@ -0,0 +1,4 @@ +{% load i18n %} +{% autoescape off %} +{% blocktrans %}Password Reset{% endblocktrans %} +{% endautoescape %} diff --git a/allauth/templates/account/email/password_set_message.txt b/allauth/templates/account/email/password_set_message.txt new file mode 100644 index 0000000000..44ca0f4bce --- /dev/null +++ b/allauth/templates/account/email/password_set_message.txt @@ -0,0 +1,4 @@ +{% extends "account/email/base_notification.txt" %} +{% load i18n %} + +{% block notification_message %}{% blocktrans %}Your password has been set.{% endblocktrans %}{% endblock notification_message %} diff --git a/allauth/templates/account/email/password_set_subject.txt b/allauth/templates/account/email/password_set_subject.txt new file mode 100644 index 0000000000..fc760840e5 --- /dev/null +++ b/allauth/templates/account/email/password_set_subject.txt @@ -0,0 +1,4 @@ +{% load i18n %} +{% autoescape off %} +{% blocktrans %}Password Set{% endblocktrans %} +{% endautoescape %} diff --git a/allauth/templates/mfa/email/recovery_codes_generated_message.txt b/allauth/templates/mfa/email/recovery_codes_generated_message.txt new file mode 100644 index 0000000000..34a0c85711 --- /dev/null +++ b/allauth/templates/mfa/email/recovery_codes_generated_message.txt @@ -0,0 +1,4 @@ +{% extends "account/email/base_notification.txt" %} +{% load i18n %} + +{% block notification_message %}{% blocktrans %}Recovery codes has been generated.{% endblocktrans %}{% endblock notification_message %} diff --git a/allauth/templates/mfa/email/recovery_codes_generated_subject.txt b/allauth/templates/mfa/email/recovery_codes_generated_subject.txt new file mode 100644 index 0000000000..f806534f77 --- /dev/null +++ b/allauth/templates/mfa/email/recovery_codes_generated_subject.txt @@ -0,0 +1,4 @@ +{% load i18n %} +{% autoescape off %} +{% blocktrans %}Recovery codes generated{% endblocktrans %} +{% endautoescape %} diff --git a/allauth/templates/mfa/email/totp_activated_message.txt b/allauth/templates/mfa/email/totp_activated_message.txt new file mode 100644 index 0000000000..97e115ba3b --- /dev/null +++ b/allauth/templates/mfa/email/totp_activated_message.txt @@ -0,0 +1,4 @@ +{% extends "account/email/base_notification.txt" %} +{% load i18n %} + +{% block notification_message %}{% blocktrans %}Authenticator App has been activated.{% endblocktrans %}{% endblock notification_message %} diff --git a/allauth/templates/mfa/email/totp_activated_subject.txt b/allauth/templates/mfa/email/totp_activated_subject.txt new file mode 100644 index 0000000000..05f734bb0f --- /dev/null +++ b/allauth/templates/mfa/email/totp_activated_subject.txt @@ -0,0 +1,4 @@ +{% load i18n %} +{% autoescape off %} +{% blocktrans %}Authenticator App Activated{% endblocktrans %} +{% endautoescape %} diff --git a/allauth/templates/mfa/email/totp_deactivated_message.txt b/allauth/templates/mfa/email/totp_deactivated_message.txt new file mode 100644 index 0000000000..2160ad798f --- /dev/null +++ b/allauth/templates/mfa/email/totp_deactivated_message.txt @@ -0,0 +1,4 @@ +{% extends "account/email/base_notification.txt" %} +{% load i18n %} + +{% block notification_message %}{% blocktrans %}Authenticator App has been deactivated.{% endblocktrans %}{% endblock notification_message %} diff --git a/allauth/templates/mfa/email/totp_deactivated_subject.txt b/allauth/templates/mfa/email/totp_deactivated_subject.txt new file mode 100644 index 0000000000..ff8b1131e7 --- /dev/null +++ b/allauth/templates/mfa/email/totp_deactivated_subject.txt @@ -0,0 +1,4 @@ +{% load i18n %} +{% autoescape off %} +{% blocktrans %}Authenticator App deactivated{% endblocktrans %} +{% endautoescape %} diff --git a/allauth/templates/socialaccount/email/account_added_message.txt b/allauth/templates/socialaccount/email/account_added_message.txt new file mode 100644 index 0000000000..9a486cf03f --- /dev/null +++ b/allauth/templates/socialaccount/email/account_added_message.txt @@ -0,0 +1,4 @@ +{% extends "account/email/base_notification.txt" %} +{% load i18n %} + +{% block notification_message %}{% blocktrans %}A new social account has been added.{% endblocktrans %}{% endblock notification_message %} diff --git a/allauth/templates/socialaccount/email/account_added_subject.txt b/allauth/templates/socialaccount/email/account_added_subject.txt new file mode 100644 index 0000000000..13f95adf04 --- /dev/null +++ b/allauth/templates/socialaccount/email/account_added_subject.txt @@ -0,0 +1,4 @@ +{% load i18n %} +{% autoescape off %} +{% blocktrans %}Social account added{% endblocktrans %} +{% endautoescape %} diff --git a/allauth/templates/socialaccount/email/account_disconnected_message.txt b/allauth/templates/socialaccount/email/account_disconnected_message.txt new file mode 100644 index 0000000000..951ca8ec3a --- /dev/null +++ b/allauth/templates/socialaccount/email/account_disconnected_message.txt @@ -0,0 +1,4 @@ +{% extends "account/email/base_notification.txt" %} +{% load i18n %} + +{% block notification_message %}{% blocktrans %}Your social account has been disconnected{% endblocktrans %}{% endblock notification_message %} diff --git a/allauth/templates/socialaccount/email/account_disconnected_subject.txt b/allauth/templates/socialaccount/email/account_disconnected_subject.txt new file mode 100644 index 0000000000..fa2604105e --- /dev/null +++ b/allauth/templates/socialaccount/email/account_disconnected_subject.txt @@ -0,0 +1,4 @@ +{% load i18n %} +{% autoescape off %} +{% blocktrans %}Social account disconnected{% endblocktrans %} +{% endautoescape %} diff --git a/allauth/templates/socialaccount/email/account_updated_message.txt b/allauth/templates/socialaccount/email/account_updated_message.txt new file mode 100644 index 0000000000..da4b00b8c8 --- /dev/null +++ b/allauth/templates/socialaccount/email/account_updated_message.txt @@ -0,0 +1,4 @@ +{% extends "account/email/base_notification.txt" %} +{% load i18n %} + +{% block notification_message %}{% blocktrans %}Your social account has been updated.{% endblocktrans %}{% endblock notification_message %} diff --git a/allauth/templates/socialaccount/email/account_updated_subject.txt b/allauth/templates/socialaccount/email/account_updated_subject.txt new file mode 100644 index 0000000000..dc0fc7bd5f --- /dev/null +++ b/allauth/templates/socialaccount/email/account_updated_subject.txt @@ -0,0 +1,4 @@ +{% load i18n %} +{% autoescape off %} +{% blocktrans %}Social account updated{% endblocktrans %} +{% endautoescape %}