Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit 75f7a7b

Browse files
[1/2] Allow homeservers to send registration emails | Sending the email (#5835)
~~Fixes #5833 Moved out to ~~#5863 Part of fixing #5751 Decouples the activity of sending registration emails and binding them to an identity server. This PR simply sends the registration email, but clicking it does not approve the user for registration. That will come in PR #2. Some of this makes use of existing stuff for sending password reset emails from Synapse. Some work was done to make that stuff even more generic. Upgrade notes: * There is a new top-level config option, `account_threepid_delegate` which defines the address of an identity server that you would like to send registration/password reset emails on your behalf. The option `email.trust_identity_server_for_password_resets` has been replaced by this. If you set `email.trust_identity_server_for_password_resets` in your config to `true`, please remove it and configure `account_threepid_delegate` instead. The [sample config](https://github.com/matrix-org/synapse/blob/anoa/reg_email_sending_email/docs/sample_config.yaml) has information on how to configure it. Note: This PR does not allow homeservers to send emails when simply adding an email to your account. That will come after this and will be blocked on a new MSC. Part [2/2] will be successfully completing the registration step when `/submit_token` is hit with the correct details, and clearing out the `password_servlet` flag stuff, which is no longer needed. Will be a much smaller PR than this one. ~~Requires #5863 has been merged into the base branch. ~~Requires #5876 has been merged into the base branch.
1 parent 436a207 commit 75f7a7b

18 files changed

+436
-154
lines changed

UPGRADE.rst

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,55 @@ returned by the Client-Server API:
4949
# configured on port 443.
5050
curl -kv https://<host.name>/_matrix/client/versions 2>&1 | grep "Server:"
5151
52+
Upgrading to v1.4.0
53+
===================
54+
55+
Config options
56+
--------------
57+
58+
**Note: Registration by email address or phone number will not work in this release unless
59+
some config options are changed from their defaults.**
60+
61+
This is due to Synapse v1.4.0 now defaulting to sending registration and password reset tokens
62+
itself. This is for security reasons as well as putting less reliance on identity servers.
63+
However, currently Synapse only supports sending emails, and does not have support for
64+
phone-based password reset or account registration. If Synapse is configured to handle these on
65+
its own, phone-based password resets and registration will be disabled. For Synapse to send
66+
emails, the ``email`` block of the config must be filled out. If not, then password resets and
67+
registration via email will be disabled entirely.
68+
69+
This release also deprecates the ``email.trust_identity_server_for_password_resets`` option
70+
and replaces it with ``account_threepid_delegate``. This option defines whether the homeserver
71+
should delegate an external server (typically an `identity server
72+
<https://matrix.org/docs/spec/identity_service/r0.2.1>`_) to handle sending password reset
73+
or registration messages via email or SMS.
74+
75+
If ``email.trust_identity_server_for_password_resets`` was changed from its default to
76+
``true``, and ``account_threepid_delegate`` is not set to an identity server domain, then the
77+
server handling password resets and registration via third-party addresses will be set to the
78+
first entry in the Synapse config's ``trusted_third_party_id_servers`` entry. If no domains are
79+
configured, Synapse will throw an error on startup.
80+
81+
If ``email.trust_identity_server_for_password_resets`` is not set to ``true`` and
82+
``account_threepid_delegate`` is not set to a domain, then Synapse will attempt to send
83+
password reset and registration messages itself.
84+
85+
Email templates
86+
---------------
87+
88+
If you have configured a custom template directory with the ``email.template_dir`` option, be
89+
aware that there are new templates regarding registration. ``registration.html`` and
90+
``registration.txt`` have been added and contain the text that is sent to a client upon
91+
registering via email address.
92+
93+
``registration_success.html`` and ``registration_failure.html`` are templates containing HTML
94+
that will be shown to the user when they click the link in their registration email (if a
95+
redirect URL is not configured), either showing them a success or failure page.
96+
97+
Synapse will expect these files to exist inside the configured template directory. To view the
98+
default templates, see `synapse/res/templates
99+
<https://github.com/matrix-org/synapse/tree/master/synapse/res/templates>`_.
100+
52101
Upgrading to v1.2.0
53102
===================
54103

@@ -132,6 +181,19 @@ server for password resets, set ``trust_identity_server_for_password_resets`` to
132181
See the `sample configuration file <docs/sample_config.yaml>`_
133182
for more details on these settings.
134183

184+
New email templates
185+
---------------
186+
Some new templates have been added to the default template directory for the purpose of the
187+
homeserver sending its own password reset emails. If you have configured a custom
188+
``template_dir`` in your Synapse config, these files will need to be added.
189+
190+
``password_reset.html`` and ``password_reset.txt`` are HTML and plain text templates
191+
respectively that contain the contents of what will be emailed to the user upon attempting to
192+
reset their password via email. ``password_reset_success.html`` and
193+
``password_reset_failure.html`` are HTML files that the content of which (assuming no redirect
194+
URL is set) will be shown to the user after they attempt to click the link in the email sent
195+
to them.
196+
135197
Upgrading to v0.99.0
136198
====================
137199

changelog.d/5835.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add the ability to send registration emails from the homeserver rather than delegating to an identity server.

docs/sample_config.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1217,11 +1217,22 @@ password_config:
12171217
# #password_reset_template_html: password_reset.html
12181218
# #password_reset_template_text: password_reset.txt
12191219
#
1220+
# # Templates for registration emails sent by the homeserver
1221+
# #
1222+
# #registration_template_html: registration.html
1223+
# #registration_template_text: registration.txt
1224+
#
12201225
# # Templates for password reset success and failure pages that a user
12211226
# # will see after attempting to reset their password
12221227
# #
12231228
# #password_reset_template_success_html: password_reset_success.html
12241229
# #password_reset_template_failure_html: password_reset_failure.html
1230+
#
1231+
# # Templates for registration success and failure pages that a user
1232+
# # will see after attempting to register using an email or phone
1233+
# #
1234+
# #registration_template_success_html: registration_success.html
1235+
# #registration_template_failure_html: registration_failure.html
12251236

12261237

12271238
#password_providers:

synapse/config/emailconfig.py

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def read_config(self, config, **kwargs):
7575
"renew_at"
7676
)
7777

78-
self.email_threepid_behaviour = (
78+
self.threepid_behaviour = (
7979
# Have Synapse handle the email sending if account_threepid_delegate
8080
# is not defined
8181
ThreepidBehaviour.REMOTE
@@ -87,9 +87,14 @@ def read_config(self, config, **kwargs):
8787
# if they have this set and tell them to use the updated option, while using a default
8888
# identity server in the process.
8989
self.using_identity_server_from_trusted_list = False
90-
if config.get("trust_identity_server_for_password_resets", False) is True:
90+
if (
91+
not self.account_threepid_delegate
92+
and config.get("trust_identity_server_for_password_resets", False) is True
93+
):
9194
# Use the first entry in self.trusted_third_party_id_servers instead
9295
if self.trusted_third_party_id_servers:
96+
# XXX: It's a little confusing that account_threepid_delegate is modifed
97+
# both in RegistrationConfig and here. We should factor this bit out
9398
self.account_threepid_delegate = self.trusted_third_party_id_servers[0]
9499
self.using_identity_server_from_trusted_list = True
95100
else:
@@ -98,16 +103,13 @@ def read_config(self, config, **kwargs):
98103
'"trusted_third_party_id_servers" but it is empty.'
99104
)
100105

101-
self.local_threepid_emails_disabled_due_to_config = False
102-
if (
103-
self.email_threepid_behaviour == ThreepidBehaviour.LOCAL
104-
and email_config == {}
105-
):
106+
self.local_threepid_handling_disabled_due_to_email_config = False
107+
if self.threepid_behaviour == ThreepidBehaviour.LOCAL and email_config == {}:
106108
# We cannot warn the user this has happened here
107109
# Instead do so when a user attempts to reset their password
108-
self.local_threepid_emails_disabled_due_to_config = True
110+
self.local_threepid_handling_disabled_due_to_email_config = True
109111

110-
self.email_threepid_behaviour = ThreepidBehaviour.OFF
112+
self.threepid_behaviour = ThreepidBehaviour.OFF
111113

112114
# Get lifetime of a validation token in milliseconds
113115
self.email_validation_token_lifetime = self.parse_duration(
@@ -117,7 +119,7 @@ def read_config(self, config, **kwargs):
117119
if (
118120
self.email_enable_notifs
119121
or account_validity_renewal_enabled
120-
or self.email_threepid_behaviour == ThreepidBehaviour.LOCAL
122+
or self.threepid_behaviour == ThreepidBehaviour.LOCAL
121123
):
122124
# make sure we can import the required deps
123125
import jinja2
@@ -127,7 +129,7 @@ def read_config(self, config, **kwargs):
127129
jinja2
128130
bleach
129131

130-
if self.email_threepid_behaviour == ThreepidBehaviour.LOCAL:
132+
if self.threepid_behaviour == ThreepidBehaviour.LOCAL:
131133
required = ["smtp_host", "smtp_port", "notif_from"]
132134

133135
missing = []
@@ -146,28 +148,45 @@ def read_config(self, config, **kwargs):
146148
% (", ".join(missing),)
147149
)
148150

149-
# Templates for password reset emails
151+
# These email templates have placeholders in them, and thus must be
152+
# parsed using a templating engine during a request
150153
self.email_password_reset_template_html = email_config.get(
151154
"password_reset_template_html", "password_reset.html"
152155
)
153156
self.email_password_reset_template_text = email_config.get(
154157
"password_reset_template_text", "password_reset.txt"
155158
)
159+
self.email_registration_template_html = email_config.get(
160+
"registration_template_html", "registration.html"
161+
)
162+
self.email_registration_template_text = email_config.get(
163+
"registration_template_text", "registration.txt"
164+
)
156165
self.email_password_reset_template_failure_html = email_config.get(
157166
"password_reset_template_failure_html", "password_reset_failure.html"
158167
)
159-
# This template does not support any replaceable variables, so we will
160-
# read it from the disk once during setup
168+
self.email_registration_template_failure_html = email_config.get(
169+
"registration_template_failure_html", "registration_failure.html"
170+
)
171+
172+
# These templates do not support any placeholder variables, so we
173+
# will read them from disk once during setup
161174
email_password_reset_template_success_html = email_config.get(
162175
"password_reset_template_success_html", "password_reset_success.html"
163176
)
177+
email_registration_template_success_html = email_config.get(
178+
"registration_template_success_html", "registration_success.html"
179+
)
164180

165181
# Check templates exist
166182
for f in [
167183
self.email_password_reset_template_html,
168184
self.email_password_reset_template_text,
185+
self.email_registration_template_html,
186+
self.email_registration_template_text,
169187
self.email_password_reset_template_failure_html,
170188
email_password_reset_template_success_html,
189+
email_registration_template_success_html,
171190
]:
172191
p = os.path.join(self.email_template_dir, f)
173192
if not os.path.isfile(p):
@@ -177,9 +196,15 @@ def read_config(self, config, **kwargs):
177196
filepath = os.path.join(
178197
self.email_template_dir, email_password_reset_template_success_html
179198
)
180-
self.email_password_reset_template_success_html_content = self.read_file(
199+
self.email_password_reset_template_success_html = self.read_file(
181200
filepath, "email.password_reset_template_success_html"
182201
)
202+
filepath = os.path.join(
203+
self.email_template_dir, email_registration_template_success_html
204+
)
205+
self.email_registration_template_success_html_content = self.read_file(
206+
filepath, "email.registration_template_success_html"
207+
)
183208

184209
if self.email_enable_notifs:
185210
required = [
@@ -291,11 +316,22 @@ def generate_config_section(self, config_dir_path, server_name, **kwargs):
291316
# #password_reset_template_html: password_reset.html
292317
# #password_reset_template_text: password_reset.txt
293318
#
319+
# # Templates for registration emails sent by the homeserver
320+
# #
321+
# #registration_template_html: registration.html
322+
# #registration_template_text: registration.txt
323+
#
294324
# # Templates for password reset success and failure pages that a user
295325
# # will see after attempting to reset their password
296326
# #
297327
# #password_reset_template_success_html: password_reset_success.html
298328
# #password_reset_template_failure_html: password_reset_failure.html
329+
#
330+
# # Templates for registration success and failure pages that a user
331+
# # will see after attempting to register using an email or phone
332+
# #
333+
# #registration_template_success_html: registration_success.html
334+
# #registration_template_failure_html: registration_failure.html
299335
"""
300336

301337

synapse/handlers/auth.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -461,10 +461,10 @@ def _check_threepid(self, medium, authdict, password_servlet=False, **kwargs):
461461
logger.info("Getting validated threepid. threepidcreds: %r", (threepid_creds,))
462462
if (
463463
not password_servlet
464-
or self.hs.config.email_threepid_behaviour == ThreepidBehaviour.REMOTE
464+
or self.hs.config.threepid_behaviour == ThreepidBehaviour.REMOTE
465465
):
466466
threepid = yield identity_handler.threepid_from_creds(threepid_creds)
467-
elif self.hs.config.email_threepid_behaviour == ThreepidBehaviour.LOCAL:
467+
elif self.hs.config.threepid_behaviour == ThreepidBehaviour.LOCAL:
468468
row = yield self.store.get_threepid_validation_session(
469469
medium,
470470
threepid_creds["client_secret"],

synapse/handlers/identity.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from twisted.internet import defer
2525

2626
from synapse.api.errors import CodeMessageException, HttpResponseException, SynapseError
27+
from synapse.util.stringutils import random_string
2728

2829
from ._base import BaseHandler
2930

@@ -196,6 +197,84 @@ def try_unbind_threepid_with_id_server(self, mxid, threepid, id_server):
196197

197198
return changed
198199

200+
@defer.inlineCallbacks
201+
def send_threepid_validation(
202+
self,
203+
email_address,
204+
client_secret,
205+
send_attempt,
206+
send_email_func,
207+
next_link=None,
208+
):
209+
"""Send a threepid validation email for password reset or
210+
registration purposes
211+
212+
Args:
213+
email_address (str): The user's email address
214+
client_secret (str): The provided client secret
215+
send_attempt (int): Which send attempt this is
216+
send_email_func (func): A function that takes an email address, token,
217+
client_secret and session_id, sends an email
218+
and returns a Deferred.
219+
next_link (str|None): The URL to redirect the user to after validation
220+
221+
Returns:
222+
The new session_id upon success
223+
224+
Raises:
225+
SynapseError is an error occurred when sending the email
226+
"""
227+
# Check that this email/client_secret/send_attempt combo is new or
228+
# greater than what we've seen previously
229+
session = yield self.store.get_threepid_validation_session(
230+
"email", client_secret, address=email_address, validated=False
231+
)
232+
233+
# Check to see if a session already exists and that it is not yet
234+
# marked as validated
235+
if session and session.get("validated_at") is None:
236+
session_id = session["session_id"]
237+
last_send_attempt = session["last_send_attempt"]
238+
239+
# Check that the send_attempt is higher than previous attempts
240+
if send_attempt <= last_send_attempt:
241+
# If not, just return a success without sending an email
242+
return session_id
243+
else:
244+
# An non-validated session does not exist yet.
245+
# Generate a session id
246+
session_id = random_string(16)
247+
248+
# Generate a new validation token
249+
token = random_string(32)
250+
251+
# Send the mail with the link containing the token, client_secret
252+
# and session_id
253+
try:
254+
yield send_email_func(email_address, token, client_secret, session_id)
255+
except Exception:
256+
logger.exception(
257+
"Error sending threepid validation email to %s", email_address
258+
)
259+
raise SynapseError(500, "An error was encountered when sending the email")
260+
261+
token_expires = (
262+
self.hs.clock.time_msec() + self.hs.config.email_validation_token_lifetime
263+
)
264+
265+
yield self.store.start_or_continue_validation_session(
266+
"email",
267+
email_address,
268+
session_id,
269+
client_secret,
270+
send_attempt,
271+
next_link,
272+
token,
273+
token_expires,
274+
)
275+
276+
return session_id
277+
199278
@defer.inlineCallbacks
200279
def requestEmailToken(
201280
self, id_server, email, client_secret, send_attempt, next_link=None

synapse/handlers/register.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -414,18 +414,6 @@ def register_email(self, threepidCreds):
414414
if not check_3pid_allowed(self.hs, threepid["medium"], threepid["address"]):
415415
raise RegistrationError(403, "Third party identifier is not allowed")
416416

417-
@defer.inlineCallbacks
418-
def bind_emails(self, user_id, threepidCreds):
419-
"""Links emails with a user ID and informs an identity server.
420-
421-
Used only by c/s api v1
422-
"""
423-
424-
# Now we have a matrix ID, bind it to the threepids we were given
425-
for c in threepidCreds:
426-
# XXX: This should be a deferred list, shouldn't it?
427-
yield self.identity_handler.bind_threepid(c, user_id)
428-
429417
def check_user_id_not_appservice_exclusive(self, user_id, allowed_appservice=None):
430418
# don't allow people to register the server notices mxid
431419
if self._server_notices_mxid is not None:

0 commit comments

Comments
 (0)