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

Commit

Permalink
Improve performance of the register endpoint (#8009)
Browse files Browse the repository at this point in the history
  • Loading branch information
clokep authored Aug 6, 2020
1 parent 079bc3c commit 66f2444
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 74 deletions.
1 change: 1 addition & 0 deletions changelog.d/8009.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improve the performance of the register endpoint.
4 changes: 3 additions & 1 deletion synapse/api/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,14 +238,16 @@ class InteractiveAuthIncompleteError(Exception):
(This indicates we should return a 401 with 'result' as the body)
Attributes:
session_id: The ID of the ongoing interactive auth session.
result: the server response to the request, which should be
passed back to the client
"""

def __init__(self, result: "JsonDict"):
def __init__(self, session_id: str, result: "JsonDict"):
super(InteractiveAuthIncompleteError, self).__init__(
"Interactive auth not yet complete"
)
self.session_id = session_id
self.result = result


Expand Down
19 changes: 12 additions & 7 deletions synapse/handlers/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ async def validate_user_via_ui_auth(
request_body: Dict[str, Any],
clientip: str,
description: str,
) -> dict:
) -> Tuple[dict, str]:
"""
Checks that the user is who they claim to be, via a UI auth.
Expand All @@ -183,9 +183,14 @@ async def validate_user_via_ui_auth(
describes the operation happening on their account.
Returns:
The parameters for this request (which may
A tuple of (params, session_id).
'params' contains the parameters for this request (which may
have been given only in a previous call).
'session_id' is the ID of this session, either passed in by the
client or assigned by this call
Raises:
InteractiveAuthIncompleteError if the client has not yet completed
any of the permitted login flows
Expand All @@ -207,7 +212,7 @@ async def validate_user_via_ui_auth(
flows = [[login_type] for login_type in self._supported_ui_auth_types]

try:
result, params, _ = await self.check_auth(
result, params, session_id = await self.check_ui_auth(
flows, request, request_body, clientip, description
)
except LoginError:
Expand All @@ -230,7 +235,7 @@ async def validate_user_via_ui_auth(
if user_id != requester.user.to_string():
raise AuthError(403, "Invalid auth")

return params
return params, session_id

def get_enabled_auth_types(self):
"""Return the enabled user-interactive authentication types
Expand All @@ -240,7 +245,7 @@ def get_enabled_auth_types(self):
"""
return self.checkers.keys()

async def check_auth(
async def check_ui_auth(
self,
flows: List[List[str]],
request: SynapseRequest,
Expand Down Expand Up @@ -363,7 +368,7 @@ async def check_auth(

if not authdict:
raise InteractiveAuthIncompleteError(
self._auth_dict_for_flows(flows, session.session_id)
session.session_id, self._auth_dict_for_flows(flows, session.session_id)
)

# check auth type currently being presented
Expand Down Expand Up @@ -410,7 +415,7 @@ async def check_auth(
ret = self._auth_dict_for_flows(flows, session.session_id)
ret["completed"] = list(creds)
ret.update(errordict)
raise InteractiveAuthIncompleteError(ret)
raise InteractiveAuthIncompleteError(session.session_id, ret)

async def add_oob_auth(
self, stagetype: str, authdict: Dict[str, Any], clientip: str
Expand Down
86 changes: 60 additions & 26 deletions synapse/rest/client/v2_alpha/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@
from http import HTTPStatus

from synapse.api.constants import LoginType
from synapse.api.errors import Codes, SynapseError, ThreepidValidationError
from synapse.api.errors import (
Codes,
InteractiveAuthIncompleteError,
SynapseError,
ThreepidValidationError,
)
from synapse.config.emailconfig import ThreepidBehaviour
from synapse.http.server import finish_request, respond_with_html
from synapse.http.servlet import (
Expand Down Expand Up @@ -239,18 +244,12 @@ async def on_POST(self, request):

# we do basic sanity checks here because the auth layer will store these
# in sessions. Pull out the new password provided to us.
if "new_password" in body:
new_password = body.pop("new_password")
new_password = body.pop("new_password", None)
if new_password is not None:
if not isinstance(new_password, str) or len(new_password) > 512:
raise SynapseError(400, "Invalid password")
self.password_policy_handler.validate_password(new_password)

# If the password is valid, hash it and store it back on the body.
# This ensures that only the hashed password is handled everywhere.
if "new_password_hash" in body:
raise SynapseError(400, "Unexpected property: new_password_hash")
body["new_password_hash"] = await self.auth_handler.hash(new_password)

# there are two possibilities here. Either the user does not have an
# access token, and needs to do a password reset; or they have one and
# need to validate their identity.
Expand All @@ -263,23 +262,49 @@ async def on_POST(self, request):

if self.auth.has_access_token(request):
requester = await self.auth.get_user_by_req(request)
params = await self.auth_handler.validate_user_via_ui_auth(
requester,
request,
body,
self.hs.get_ip_from_request(request),
"modify your account password",
)
try:
params, session_id = await self.auth_handler.validate_user_via_ui_auth(
requester,
request,
body,
self.hs.get_ip_from_request(request),
"modify your account password",
)
except InteractiveAuthIncompleteError as e:
# The user needs to provide more steps to complete auth, but
# they're not required to provide the password again.
#
# If a password is available now, hash the provided password and
# store it for later.
if new_password:
password_hash = await self.auth_handler.hash(new_password)
await self.auth_handler.set_session_data(
e.session_id, "password_hash", password_hash
)
raise
user_id = requester.user.to_string()
else:
requester = None
result, params, _ = await self.auth_handler.check_auth(
[[LoginType.EMAIL_IDENTITY]],
request,
body,
self.hs.get_ip_from_request(request),
"modify your account password",
)
try:
result, params, session_id = await self.auth_handler.check_ui_auth(
[[LoginType.EMAIL_IDENTITY]],
request,
body,
self.hs.get_ip_from_request(request),
"modify your account password",
)
except InteractiveAuthIncompleteError as e:
# The user needs to provide more steps to complete auth, but
# they're not required to provide the password again.
#
# If a password is available now, hash the provided password and
# store it for later.
if new_password:
password_hash = await self.auth_handler.hash(new_password)
await self.auth_handler.set_session_data(
e.session_id, "password_hash", password_hash
)
raise

if LoginType.EMAIL_IDENTITY in result:
threepid = result[LoginType.EMAIL_IDENTITY]
Expand All @@ -304,12 +329,21 @@ async def on_POST(self, request):
logger.error("Auth succeeded but no known type! %r", result.keys())
raise SynapseError(500, "", Codes.UNKNOWN)

assert_params_in_dict(params, ["new_password_hash"])
new_password_hash = params["new_password_hash"]
# If we have a password in this request, prefer it. Otherwise, there
# must be a password hash from an earlier request.
if new_password:
password_hash = await self.auth_handler.hash(new_password)
else:
password_hash = await self.auth_handler.get_session_data(
session_id, "password_hash", None
)
if not password_hash:
raise SynapseError(400, "Missing params: password", Codes.MISSING_PARAM)

logout_devices = params.get("logout_devices", True)

await self._set_password_handler.set_password(
user_id, new_password_hash, logout_devices, requester
user_id, password_hash, logout_devices, requester
)

return 200, {}
Expand Down
Loading

0 comments on commit 66f2444

Please sign in to comment.