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

Commit

Permalink
Show a confirmation page during user password reset (#8004)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
anoadragon453 authored Sep 10, 2020
1 parent e44e9ee commit a3a90ee
Show file tree
Hide file tree
Showing 16 changed files with 271 additions and 90 deletions.
24 changes: 24 additions & 0 deletions UPGRADE.rst
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,30 @@ for example:
wget https://packages.matrix.org/debian/pool/main/m/matrix-synapse-py3/matrix-synapse-py3_1.3.0+stretch1_amd64.deb
dpkg -i matrix-synapse-py3_1.3.0+stretch1_amd64.deb
Upgrading to v1.21.0
====================

New HTML templates
------------------

A new HTML template,
`password_reset_confirmation.html <https://github.com/matrix-org/synapse/blob/develop/synapse/res/templates/password_reset_confirmation.html>`_,
has been added to the ``synapse/res/templates`` directory. If you are using a
custom template directory, you may want to copy the template over and modify it.

Note that as of v1.20.0, templates do not need to be included in custom template
directories for Synapse to start. The default templates will be used if a custom
template cannot be found.

This page will appear to the user after clicking a password reset link that has
been emailed to them.

To complete password reset, the page must include a way to make a `POST`
request to
``/_synapse/client/password_reset/{medium}/submit_token``
with the query parameters from the original link, presented as a URL-encoded form. See the file
itself for more details.

Upgrading to v1.18.0
====================

Expand Down
1 change: 1 addition & 0 deletions changelog.d/8004.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Require the user to confirm that their password should be reset after clicking the email confirmation link.
10 changes: 7 additions & 3 deletions docs/sample_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2039,9 +2039,13 @@ email:
# * The contents of password reset emails sent by the homeserver:
# 'password_reset.html' and 'password_reset.txt'
#
# * HTML pages for success and failure that a user will see when they follow
# the link in the password reset email: 'password_reset_success.html' and
# 'password_reset_failure.html'
# * An HTML page that a user will see when they follow the link in the password
# reset email. The user will be asked to confirm the action before their
# password is reset: 'password_reset_confirmation.html'
#
# * HTML pages for success and failure that a user will see when they confirm
# the password reset flow using the page above: 'password_reset_success.html'
# and 'password_reset_failure.html'
#
# * The contents of address verification emails sent during registration:
# 'registration.html' and 'registration.txt'
Expand Down
1 change: 1 addition & 0 deletions synapse/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

from synapse.config import ConfigError

SYNAPSE_CLIENT_API_PREFIX = "/_synapse/client"
CLIENT_API_PREFIX = "/_matrix/client"
FEDERATION_PREFIX = "/_matrix/federation"
FEDERATION_V1_PREFIX = FEDERATION_PREFIX + "/v1"
Expand Down
10 changes: 10 additions & 0 deletions synapse/app/homeserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
from synapse.app import _base
from synapse.app._base import listen_ssl, listen_tcp, quit_with_error
from synapse.config._base import ConfigError
from synapse.config.emailconfig import ThreepidBehaviour
from synapse.config.homeserver import HomeServerConfig
from synapse.config.server import ListenerConfig
from synapse.federation.transport.server import TransportLayerServer
Expand Down Expand Up @@ -209,6 +210,15 @@ def _configure_named_resource(self, name, compress=False):

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

if self.get_config().threepid_behaviour_email == ThreepidBehaviour.LOCAL:
from synapse.rest.synapse.client.password_reset import (
PasswordResetSubmitTokenResource,
)

resources[
"/_synapse/client/password_reset/email/submit_token"
] = PasswordResetSubmitTokenResource(self)

if name == "consent":
from synapse.rest.consent.consent_resource import ConsentResource

Expand Down
12 changes: 9 additions & 3 deletions synapse/config/emailconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ def read_config(self, config, **kwargs):
self.email_registration_template_text,
self.email_add_threepid_template_html,
self.email_add_threepid_template_text,
self.email_password_reset_template_confirmation_html,
self.email_password_reset_template_failure_html,
self.email_registration_template_failure_html,
self.email_add_threepid_template_failure_html,
Expand All @@ -242,6 +243,7 @@ def read_config(self, config, **kwargs):
registration_template_text,
add_threepid_template_html,
add_threepid_template_text,
"password_reset_confirmation.html",
password_reset_template_failure_html,
registration_template_failure_html,
add_threepid_template_failure_html,
Expand Down Expand Up @@ -404,9 +406,13 @@ def generate_config_section(self, config_dir_path, server_name, **kwargs):
# * The contents of password reset emails sent by the homeserver:
# 'password_reset.html' and 'password_reset.txt'
#
# * HTML pages for success and failure that a user will see when they follow
# the link in the password reset email: 'password_reset_success.html' and
# 'password_reset_failure.html'
# * An HTML page that a user will see when they follow the link in the password
# reset email. The user will be asked to confirm the action before their
# password is reset: 'password_reset_confirmation.html'
#
# * HTML pages for success and failure that a user will see when they confirm
# the password reset flow using the page above: 'password_reset_success.html'
# and 'password_reset_failure.html'
#
# * The contents of address verification emails sent during registration:
# 'registration.html' and 'registration.txt'
Expand Down
2 changes: 1 addition & 1 deletion synapse/push/mailer.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ async def send_password_reset_mail(self, email_address, token, client_secret, si
params = {"token": token, "client_secret": client_secret, "sid": sid}
link = (
self.hs.config.public_baseurl
+ "_matrix/client/unstable/password_reset/email/submit_token?%s"
+ "_synapse/client/password_reset/email/submit_token?%s"
% urllib.parse.urlencode(params)
)

Expand Down
16 changes: 16 additions & 0 deletions synapse/res/templates/password_reset_confirmation.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<html>
<head></head>
<body>
<!--Use a hidden form to resubmit the information necessary to reset the password-->
<form method="post">
<input type="hidden" name="sid" value="{{ sid }}">
<input type="hidden" name="token" value="{{ token }}">
<input type="hidden" name="client_secret" value="{{ client_secret }}">

<p>You have requested to <strong>reset your Matrix account password</strong>. Click the link below to confirm this action. <br /><br />
If you did not mean to do this, please close this page and your password will not be changed.</p>
<p><button type="submit">Confirm changing my password</button></p>
</form>
</body>
</html>

6 changes: 2 additions & 4 deletions synapse/rest/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import synapse.rest.admin
from synapse.http.server import JsonResource
from synapse.rest import admin
from synapse.rest.client import versions
from synapse.rest.client.v1 import (
directory,
Expand Down Expand Up @@ -123,9 +123,7 @@ def register_servlets(client_resource, hs):
password_policy.register_servlets(hs, client_resource)

# moving to /_synapse/admin
synapse.rest.admin.register_servlets_for_client_rest_resource(
hs, client_resource
)
admin.register_servlets_for_client_rest_resource(hs, client_resource)

# unstable
shared_rooms.register_servlets(hs, client_resource)
76 changes: 0 additions & 76 deletions synapse/rest/client/v2_alpha/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,81 +152,6 @@ async def on_POST(self, request):
return 200, ret


class PasswordResetSubmitTokenServlet(RestServlet):
"""Handles 3PID validation token submission"""

PATTERNS = client_patterns(
"/password_reset/(?P<medium>[^/]*)/submit_token$", releases=(), unstable=True
)

def __init__(self, hs):
"""
Args:
hs (synapse.server.HomeServer): server
"""
super(PasswordResetSubmitTokenServlet, self).__init__()
self.hs = hs
self.auth = hs.get_auth()
self.config = hs.config
self.clock = hs.get_clock()
self.store = hs.get_datastore()
if self.config.threepid_behaviour_email == ThreepidBehaviour.LOCAL:
self._failure_email_template = (
self.config.email_password_reset_template_failure_html
)

async def on_GET(self, request, medium):
# We currently only handle threepid token submissions for email
if medium != "email":
raise SynapseError(
400, "This medium is currently not supported for password resets"
)
if self.config.threepid_behaviour_email == ThreepidBehaviour.OFF:
if self.config.local_threepid_handling_disabled_due_to_email_config:
logger.warning(
"Password reset emails have been disabled due to lack of an email config"
)
raise SynapseError(
400, "Email-based password resets are disabled on this server"
)

sid = parse_string(request, "sid", required=True)
token = parse_string(request, "token", required=True)
client_secret = parse_string(request, "client_secret", required=True)
assert_valid_client_secret(client_secret)

# Attempt to validate a 3PID session
try:
# Mark the session as valid
next_link = await self.store.validate_threepid_session(
sid, client_secret, token, self.clock.time_msec()
)

# Perform a 302 redirect if next_link is set
if next_link:
if next_link.startswith("file:///"):
logger.warning(
"Not redirecting to next_link as it is a local file: address"
)
else:
request.setResponseCode(302)
request.setHeader("Location", next_link)
finish_request(request)
return None

# Otherwise show the success template
html = self.config.email_password_reset_template_success_html_content
status_code = 200
except ThreepidValidationError as e:
status_code = e.code

# Show a failure page with a reason
template_vars = {"failure_reason": e.msg}
html = self._failure_email_template.render(**template_vars)

respond_with_html(request, status_code, html)


class PasswordRestServlet(RestServlet):
PATTERNS = client_patterns("/account/password$")

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

def register_servlets(hs, http_server):
EmailPasswordRequestTokenRestServlet(hs).register(http_server)
PasswordResetSubmitTokenServlet(hs).register(http_server)
PasswordRestServlet(hs).register(http_server)
DeactivateAccountRestServlet(hs).register(http_server)
EmailThreepidRequestTokenRestServlet(hs).register(http_server)
Expand Down
14 changes: 14 additions & 0 deletions synapse/rest/synapse/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
# Copyright 2020 The Matrix.org Foundation C.I.C.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
14 changes: 14 additions & 0 deletions synapse/rest/synapse/client/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
# Copyright 2020 The Matrix.org Foundation C.I.C.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
Loading

0 comments on commit a3a90ee

Please sign in to comment.