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

Improve register performance #8009

Merged
merged 7 commits into from
Aug 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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