summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--changelog.d/16819.feature1
-rw-r--r--synapse/config/emailconfig.py12
-rw-r--r--synapse/push/mailer.py16
-rw-r--r--synapse/res/templates/already_in_use.html12
-rw-r--r--synapse/res/templates/already_in_use.txt10
-rw-r--r--synapse/rest/client/register.py12
-rw-r--r--tests/rest/client/test_register.py9
7 files changed, 70 insertions, 2 deletions
diff --git a/changelog.d/16819.feature b/changelog.d/16819.feature
new file mode 100644
index 0000000000..1af6f466b7
--- /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 a4dc9db03e..8033fa2e52 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 f1ffc8115f..7c15eb7440 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 0000000000..4c4c3c36a7
--- /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 %}
+<p>You have asked us to register this email with a new Matrix account, but this email is already registered with an existing account.</p>
+
+<p>Please reset your password if needed.</p>
+
+<p>If this was not you, you can safely disregard this email.</p>
+
+<p>Thank you.</p>
+{% endblock %}
diff --git a/synapse/res/templates/already_in_use.txt b/synapse/res/templates/already_in_use.txt
new file mode 100644
index 0000000000..c60401a940
--- /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 634ebed2be..5dddbc69be 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 859051cdda..694f143eff 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"