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

Commit a3a90ee

Browse files
Show a confirmation page during user password reset (#8004)
This PR adds a confirmation step to resetting your user password between clicking the link in your email and your password actually being reset. This is to better align our password reset flow with the industry standard of requiring a confirmation from the user after email validation.
1 parent e44e9ee commit a3a90ee

File tree

16 files changed

+271
-90
lines changed

16 files changed

+271
-90
lines changed

UPGRADE.rst

+24
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,30 @@ for example:
8888
wget https://packages.matrix.org/debian/pool/main/m/matrix-synapse-py3/matrix-synapse-py3_1.3.0+stretch1_amd64.deb
8989
dpkg -i matrix-synapse-py3_1.3.0+stretch1_amd64.deb
9090
91+
Upgrading to v1.21.0
92+
====================
93+
94+
New HTML templates
95+
------------------
96+
97+
A new HTML template,
98+
`password_reset_confirmation.html <https://github.com/matrix-org/synapse/blob/develop/synapse/res/templates/password_reset_confirmation.html>`_,
99+
has been added to the ``synapse/res/templates`` directory. If you are using a
100+
custom template directory, you may want to copy the template over and modify it.
101+
102+
Note that as of v1.20.0, templates do not need to be included in custom template
103+
directories for Synapse to start. The default templates will be used if a custom
104+
template cannot be found.
105+
106+
This page will appear to the user after clicking a password reset link that has
107+
been emailed to them.
108+
109+
To complete password reset, the page must include a way to make a `POST`
110+
request to
111+
``/_synapse/client/password_reset/{medium}/submit_token``
112+
with the query parameters from the original link, presented as a URL-encoded form. See the file
113+
itself for more details.
114+
91115
Upgrading to v1.18.0
92116
====================
93117

changelog.d/8004.feature

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Require the user to confirm that their password should be reset after clicking the email confirmation link.

docs/sample_config.yaml

+7-3
Original file line numberDiff line numberDiff line change
@@ -2039,9 +2039,13 @@ email:
20392039
# * The contents of password reset emails sent by the homeserver:
20402040
# 'password_reset.html' and 'password_reset.txt'
20412041
#
2042-
# * HTML pages for success and failure that a user will see when they follow
2043-
# the link in the password reset email: 'password_reset_success.html' and
2044-
# 'password_reset_failure.html'
2042+
# * An HTML page that a user will see when they follow the link in the password
2043+
# reset email. The user will be asked to confirm the action before their
2044+
# password is reset: 'password_reset_confirmation.html'
2045+
#
2046+
# * HTML pages for success and failure that a user will see when they confirm
2047+
# the password reset flow using the page above: 'password_reset_success.html'
2048+
# and 'password_reset_failure.html'
20452049
#
20462050
# * The contents of address verification emails sent during registration:
20472051
# 'registration.html' and 'registration.txt'

synapse/api/urls.py

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
from synapse.config import ConfigError
2323

24+
SYNAPSE_CLIENT_API_PREFIX = "/_synapse/client"
2425
CLIENT_API_PREFIX = "/_matrix/client"
2526
FEDERATION_PREFIX = "/_matrix/federation"
2627
FEDERATION_V1_PREFIX = FEDERATION_PREFIX + "/v1"

synapse/app/homeserver.py

+10
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
from synapse.app import _base
4949
from synapse.app._base import listen_ssl, listen_tcp, quit_with_error
5050
from synapse.config._base import ConfigError
51+
from synapse.config.emailconfig import ThreepidBehaviour
5152
from synapse.config.homeserver import HomeServerConfig
5253
from synapse.config.server import ListenerConfig
5354
from synapse.federation.transport.server import TransportLayerServer
@@ -209,6 +210,15 @@ def _configure_named_resource(self, name, compress=False):
209210

210211
resources["/_matrix/saml2"] = SAML2Resource(self)
211212

213+
if self.get_config().threepid_behaviour_email == ThreepidBehaviour.LOCAL:
214+
from synapse.rest.synapse.client.password_reset import (
215+
PasswordResetSubmitTokenResource,
216+
)
217+
218+
resources[
219+
"/_synapse/client/password_reset/email/submit_token"
220+
] = PasswordResetSubmitTokenResource(self)
221+
212222
if name == "consent":
213223
from synapse.rest.consent.consent_resource import ConsentResource
214224

synapse/config/emailconfig.py

+9-3
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ def read_config(self, config, **kwargs):
228228
self.email_registration_template_text,
229229
self.email_add_threepid_template_html,
230230
self.email_add_threepid_template_text,
231+
self.email_password_reset_template_confirmation_html,
231232
self.email_password_reset_template_failure_html,
232233
self.email_registration_template_failure_html,
233234
self.email_add_threepid_template_failure_html,
@@ -242,6 +243,7 @@ def read_config(self, config, **kwargs):
242243
registration_template_text,
243244
add_threepid_template_html,
244245
add_threepid_template_text,
246+
"password_reset_confirmation.html",
245247
password_reset_template_failure_html,
246248
registration_template_failure_html,
247249
add_threepid_template_failure_html,
@@ -404,9 +406,13 @@ def generate_config_section(self, config_dir_path, server_name, **kwargs):
404406
# * The contents of password reset emails sent by the homeserver:
405407
# 'password_reset.html' and 'password_reset.txt'
406408
#
407-
# * HTML pages for success and failure that a user will see when they follow
408-
# the link in the password reset email: 'password_reset_success.html' and
409-
# 'password_reset_failure.html'
409+
# * An HTML page that a user will see when they follow the link in the password
410+
# reset email. The user will be asked to confirm the action before their
411+
# password is reset: 'password_reset_confirmation.html'
412+
#
413+
# * HTML pages for success and failure that a user will see when they confirm
414+
# the password reset flow using the page above: 'password_reset_success.html'
415+
# and 'password_reset_failure.html'
410416
#
411417
# * The contents of address verification emails sent during registration:
412418
# 'registration.html' and 'registration.txt'

synapse/push/mailer.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ async def send_password_reset_mail(self, email_address, token, client_secret, si
123123
params = {"token": token, "client_secret": client_secret, "sid": sid}
124124
link = (
125125
self.hs.config.public_baseurl
126-
+ "_matrix/client/unstable/password_reset/email/submit_token?%s"
126+
+ "_synapse/client/password_reset/email/submit_token?%s"
127127
% urllib.parse.urlencode(params)
128128
)
129129

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<html>
2+
<head></head>
3+
<body>
4+
<!--Use a hidden form to resubmit the information necessary to reset the password-->
5+
<form method="post">
6+
<input type="hidden" name="sid" value="{{ sid }}">
7+
<input type="hidden" name="token" value="{{ token }}">
8+
<input type="hidden" name="client_secret" value="{{ client_secret }}">
9+
10+
<p>You have requested to <strong>reset your Matrix account password</strong>. Click the link below to confirm this action. <br /><br />
11+
If you did not mean to do this, please close this page and your password will not be changed.</p>
12+
<p><button type="submit">Confirm changing my password</button></p>
13+
</form>
14+
</body>
15+
</html>
16+

synapse/rest/__init__.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1414
# See the License for the specific language governing permissions and
1515
# limitations under the License.
16-
import synapse.rest.admin
1716
from synapse.http.server import JsonResource
17+
from synapse.rest import admin
1818
from synapse.rest.client import versions
1919
from synapse.rest.client.v1 import (
2020
directory,
@@ -123,9 +123,7 @@ def register_servlets(client_resource, hs):
123123
password_policy.register_servlets(hs, client_resource)
124124

125125
# moving to /_synapse/admin
126-
synapse.rest.admin.register_servlets_for_client_rest_resource(
127-
hs, client_resource
128-
)
126+
admin.register_servlets_for_client_rest_resource(hs, client_resource)
129127

130128
# unstable
131129
shared_rooms.register_servlets(hs, client_resource)

synapse/rest/client/v2_alpha/account.py

-76
Original file line numberDiff line numberDiff line change
@@ -152,81 +152,6 @@ async def on_POST(self, request):
152152
return 200, ret
153153

154154

155-
class PasswordResetSubmitTokenServlet(RestServlet):
156-
"""Handles 3PID validation token submission"""
157-
158-
PATTERNS = client_patterns(
159-
"/password_reset/(?P<medium>[^/]*)/submit_token$", releases=(), unstable=True
160-
)
161-
162-
def __init__(self, hs):
163-
"""
164-
Args:
165-
hs (synapse.server.HomeServer): server
166-
"""
167-
super(PasswordResetSubmitTokenServlet, self).__init__()
168-
self.hs = hs
169-
self.auth = hs.get_auth()
170-
self.config = hs.config
171-
self.clock = hs.get_clock()
172-
self.store = hs.get_datastore()
173-
if self.config.threepid_behaviour_email == ThreepidBehaviour.LOCAL:
174-
self._failure_email_template = (
175-
self.config.email_password_reset_template_failure_html
176-
)
177-
178-
async def on_GET(self, request, medium):
179-
# We currently only handle threepid token submissions for email
180-
if medium != "email":
181-
raise SynapseError(
182-
400, "This medium is currently not supported for password resets"
183-
)
184-
if self.config.threepid_behaviour_email == ThreepidBehaviour.OFF:
185-
if self.config.local_threepid_handling_disabled_due_to_email_config:
186-
logger.warning(
187-
"Password reset emails have been disabled due to lack of an email config"
188-
)
189-
raise SynapseError(
190-
400, "Email-based password resets are disabled on this server"
191-
)
192-
193-
sid = parse_string(request, "sid", required=True)
194-
token = parse_string(request, "token", required=True)
195-
client_secret = parse_string(request, "client_secret", required=True)
196-
assert_valid_client_secret(client_secret)
197-
198-
# Attempt to validate a 3PID session
199-
try:
200-
# Mark the session as valid
201-
next_link = await self.store.validate_threepid_session(
202-
sid, client_secret, token, self.clock.time_msec()
203-
)
204-
205-
# Perform a 302 redirect if next_link is set
206-
if next_link:
207-
if next_link.startswith("file:///"):
208-
logger.warning(
209-
"Not redirecting to next_link as it is a local file: address"
210-
)
211-
else:
212-
request.setResponseCode(302)
213-
request.setHeader("Location", next_link)
214-
finish_request(request)
215-
return None
216-
217-
# Otherwise show the success template
218-
html = self.config.email_password_reset_template_success_html_content
219-
status_code = 200
220-
except ThreepidValidationError as e:
221-
status_code = e.code
222-
223-
# Show a failure page with a reason
224-
template_vars = {"failure_reason": e.msg}
225-
html = self._failure_email_template.render(**template_vars)
226-
227-
respond_with_html(request, status_code, html)
228-
229-
230155
class PasswordRestServlet(RestServlet):
231156
PATTERNS = client_patterns("/account/password$")
232157

@@ -938,7 +863,6 @@ async def on_GET(self, request):
938863

939864
def register_servlets(hs, http_server):
940865
EmailPasswordRequestTokenRestServlet(hs).register(http_server)
941-
PasswordResetSubmitTokenServlet(hs).register(http_server)
942866
PasswordRestServlet(hs).register(http_server)
943867
DeactivateAccountRestServlet(hs).register(http_server)
944868
EmailThreepidRequestTokenRestServlet(hs).register(http_server)

synapse/rest/synapse/__init__.py

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright 2020 The Matrix.org Foundation C.I.C.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright 2020 The Matrix.org Foundation C.I.C.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.

0 commit comments

Comments
 (0)