From ae181233aa4c296d5d973eedfc599145ac0d5918 Mon Sep 17 00:00:00 2001 From: mcalinghee Date: Tue, 23 Apr 2024 17:45:24 +0200 Subject: [PATCH] Send an email if the address is already bound to an user account (#16819) Co-authored-by: Mathieu Velten Co-authored-by: Olivier D --- changelog.d/16819.feature | 1 + synapse/config/emailconfig.py | 12 ++++++++++++ synapse/push/mailer.py | 16 ++++++++++++++++ synapse/res/templates/already_in_use.html | 12 ++++++++++++ synapse/res/templates/already_in_use.txt | 10 ++++++++++ synapse/rest/client/register.py | 12 ++++++++++-- tests/rest/client/test_register.py | 9 +++++++++ 7 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 changelog.d/16819.feature create mode 100644 synapse/res/templates/already_in_use.html create mode 100644 synapse/res/templates/already_in_use.txt diff --git a/changelog.d/16819.feature b/changelog.d/16819.feature new file mode 100644 index 000000000..1af6f466b --- /dev/null +++ b/changelog.d/16819.feature @@ -0,0 +1 @@ +Send an email if the address is already bound to an user account. diff --git a/synapse/config/emailconfig.py b/synapse/config/emailconfig.py index a4dc9db03..8033fa2e5 100644 --- a/synapse/config/emailconfig.py +++ b/synapse/config/emailconfig.py @@ -52,6 +52,7 @@ DEFAULT_SUBJECTS = { "invite_from_person_to_space": "[%(app)s] %(person)s has invited you to join the %(space)s space on %(app)s...", "password_reset": "[%(server_name)s] Password reset", "email_validation": "[%(server_name)s] Validate your email", + "email_already_in_use": "[%(server_name)s] Email already in use", } LEGACY_TEMPLATE_DIR_WARNING = """ @@ -76,6 +77,7 @@ class EmailSubjectConfig: invite_from_person_to_space: str password_reset: str email_validation: str + email_already_in_use: str class EmailConfig(Config): @@ -180,6 +182,12 @@ class EmailConfig(Config): registration_template_text = email_config.get( "registration_template_text", "registration.txt" ) + already_in_use_template_html = email_config.get( + "already_in_use_template_html", "already_in_use.html" + ) + already_in_use_template_text = email_config.get( + "already_in_use_template_html", "already_in_use.txt" + ) add_threepid_template_html = email_config.get( "add_threepid_template_html", "add_threepid.html" ) @@ -215,6 +223,8 @@ class EmailConfig(Config): self.email_password_reset_template_text, self.email_registration_template_html, self.email_registration_template_text, + self.email_already_in_use_template_html, + self.email_already_in_use_template_text, self.email_add_threepid_template_html, self.email_add_threepid_template_text, self.email_password_reset_template_confirmation_html, @@ -230,6 +240,8 @@ class EmailConfig(Config): password_reset_template_text, registration_template_html, registration_template_text, + already_in_use_template_html, + already_in_use_template_text, add_threepid_template_html, add_threepid_template_text, "password_reset_confirmation.html", diff --git a/synapse/push/mailer.py b/synapse/push/mailer.py index f1ffc8115..7c15eb744 100644 --- a/synapse/push/mailer.py +++ b/synapse/push/mailer.py @@ -205,6 +205,22 @@ class Mailer: template_vars, ) + emails_sent_counter.labels("already_in_use") + + async def send_already_in_use_mail(self, email_address: str) -> None: + """Send an email if the address is already bound to an user account + + Args: + email_address: Email address we're sending to the "already in use" mail + """ + + await self.send_email( + email_address, + self.email_subjects.email_already_in_use + % {"server_name": self.hs.config.server.server_name, "app": self.app_name}, + {}, + ) + emails_sent_counter.labels("add_threepid") async def send_add_threepid_mail( diff --git a/synapse/res/templates/already_in_use.html b/synapse/res/templates/already_in_use.html new file mode 100644 index 000000000..4c4c3c36a --- /dev/null +++ b/synapse/res/templates/already_in_use.html @@ -0,0 +1,12 @@ +{% extends "_base.html" %} +{% block title %}Email already in use{% endblock %} + +{% block body %} +

You have asked us to register this email with a new Matrix account, but this email is already registered with an existing account.

+ +

Please reset your password if needed.

+ +

If this was not you, you can safely disregard this email.

+ +

Thank you.

+{% endblock %} diff --git a/synapse/res/templates/already_in_use.txt b/synapse/res/templates/already_in_use.txt new file mode 100644 index 000000000..c60401a94 --- /dev/null +++ b/synapse/res/templates/already_in_use.txt @@ -0,0 +1,10 @@ +Hello there, + +You have asked us to register this email with a new Matrix account, +but this email is already registered with an existing account. + +Please reset your password if needed. + +If this was not you, you can safely disregard this email. + +Thank you. diff --git a/synapse/rest/client/register.py b/synapse/rest/client/register.py index 634ebed2b..5dddbc69b 100644 --- a/synapse/rest/client/register.py +++ b/synapse/rest/client/register.py @@ -86,12 +86,18 @@ class EmailRegisterRequestTokenRestServlet(RestServlet): self.config = hs.config if self.hs.config.email.can_verify_email: - self.mailer = Mailer( + self.registration_mailer = Mailer( hs=self.hs, app_name=self.config.email.email_app_name, template_html=self.config.email.email_registration_template_html, template_text=self.config.email.email_registration_template_text, ) + self.already_in_use_mailer = Mailer( + hs=self.hs, + app_name=self.config.email.email_app_name, + template_html=self.config.email.email_already_in_use_template_html, + template_text=self.config.email.email_already_in_use_template_text, + ) async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]: if not self.hs.config.email.can_verify_email: @@ -139,8 +145,10 @@ class EmailRegisterRequestTokenRestServlet(RestServlet): if self.hs.config.server.request_token_inhibit_3pid_errors: # Make the client think the operation succeeded. See the rationale in the # comments for request_token_inhibit_3pid_errors. + # Still send an email to warn the user that an account already exists. # Also wait for some random amount of time between 100ms and 1s to make it # look like we did something. + await self.already_in_use_mailer.send_already_in_use_mail(email) await self.hs.get_clock().sleep(random.randint(1, 10) / 10) return 200, {"sid": random_string(16)} @@ -151,7 +159,7 @@ class EmailRegisterRequestTokenRestServlet(RestServlet): email, client_secret, send_attempt, - self.mailer.send_registration_mail, + self.registration_mailer.send_registration_mail, next_link, ) diff --git a/tests/rest/client/test_register.py b/tests/rest/client/test_register.py index 859051cdd..694f143ef 100644 --- a/tests/rest/client/test_register.py +++ b/tests/rest/client/test_register.py @@ -22,6 +22,7 @@ import datetime import os from typing import Any, Dict, List, Tuple +from unittest.mock import AsyncMock import pkg_resources @@ -42,6 +43,7 @@ from synapse.types import JsonDict from synapse.util import Clock from tests import unittest +from tests.server import ThreadedMemoryReactorClock from tests.unittest import override_config @@ -58,6 +60,13 @@ class RegisterRestServletTestCase(unittest.HomeserverTestCase): config["allow_guest_access"] = True return config + def make_homeserver( + self, reactor: ThreadedMemoryReactorClock, clock: Clock + ) -> HomeServer: + hs = super().make_homeserver(reactor, clock) + hs.get_send_email_handler()._sendmail = AsyncMock() + return hs + def test_POST_appservice_registration_valid(self) -> None: user_id = "@as_user_kermit:test" as_token = "i_am_an_app_service"