summary refs log tree commit diff
path: root/synapse/rest/client/v2_alpha
diff options
context:
space:
mode:
authorreivilibre <38398653+reivilibre@users.noreply.github.com>2021-08-17 12:57:58 +0100
committerGitHub <noreply@github.com>2021-08-17 11:57:58 +0000
commit642a42eddece60afbbd5e5a6659fa9b939238b4a (patch)
treefde46b103f636a302d6a47aadf368e19e4fc43da /synapse/rest/client/v2_alpha
parentAlways list fallback key types in /sync (#10623) (diff)
downloadsynapse-642a42eddece60afbbd5e5a6659fa9b939238b4a.tar.xz
Flatten the synapse.rest.client package (#10600)
Diffstat (limited to 'synapse/rest/client/v2_alpha')
-rw-r--r--synapse/rest/client/v2_alpha/__init__.py13
-rw-r--r--synapse/rest/client/v2_alpha/_base.py100
-rw-r--r--synapse/rest/client/v2_alpha/account.py910
-rw-r--r--synapse/rest/client/v2_alpha/account_data.py122
-rw-r--r--synapse/rest/client/v2_alpha/account_validity.py104
-rw-r--r--synapse/rest/client/v2_alpha/auth.py143
-rw-r--r--synapse/rest/client/v2_alpha/capabilities.py68
-rw-r--r--synapse/rest/client/v2_alpha/devices.py300
-rw-r--r--synapse/rest/client/v2_alpha/filter.py94
-rw-r--r--synapse/rest/client/v2_alpha/groups.py957
-rw-r--r--synapse/rest/client/v2_alpha/keys.py344
-rw-r--r--synapse/rest/client/v2_alpha/knock.py107
-rw-r--r--synapse/rest/client/v2_alpha/notifications.py91
-rw-r--r--synapse/rest/client/v2_alpha/openid.py94
-rw-r--r--synapse/rest/client/v2_alpha/password_policy.py57
-rw-r--r--synapse/rest/client/v2_alpha/read_marker.py74
-rw-r--r--synapse/rest/client/v2_alpha/receipts.py71
-rw-r--r--synapse/rest/client/v2_alpha/register.py879
-rw-r--r--synapse/rest/client/v2_alpha/relations.py381
-rw-r--r--synapse/rest/client/v2_alpha/report_event.py68
-rw-r--r--synapse/rest/client/v2_alpha/room.py441
-rw-r--r--synapse/rest/client/v2_alpha/room_keys.py391
-rw-r--r--synapse/rest/client/v2_alpha/room_upgrade_rest_servlet.py88
-rw-r--r--synapse/rest/client/v2_alpha/sendtodevice.py67
-rw-r--r--synapse/rest/client/v2_alpha/shared_rooms.py67
-rw-r--r--synapse/rest/client/v2_alpha/sync.py532
-rw-r--r--synapse/rest/client/v2_alpha/tags.py85
-rw-r--r--synapse/rest/client/v2_alpha/thirdparty.py111
-rw-r--r--synapse/rest/client/v2_alpha/tokenrefresh.py37
-rw-r--r--synapse/rest/client/v2_alpha/user_directory.py79
30 files changed, 0 insertions, 6875 deletions
diff --git a/synapse/rest/client/v2_alpha/__init__.py b/synapse/rest/client/v2_alpha/__init__.py
deleted file mode 100644
index 5e83dba2ed..0000000000
--- a/synapse/rest/client/v2_alpha/__init__.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# Copyright 2014-2016 OpenMarket Ltd
-#
-# 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.
diff --git a/synapse/rest/client/v2_alpha/_base.py b/synapse/rest/client/v2_alpha/_base.py
deleted file mode 100644
index 0443f4571c..0000000000
--- a/synapse/rest/client/v2_alpha/_base.py
+++ /dev/null
@@ -1,100 +0,0 @@
-# Copyright 2014-2016 OpenMarket Ltd
-#
-# 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.
-
-"""This module contains base REST classes for constructing client v1 servlets.
-"""
-import logging
-import re
-from typing import Iterable, Pattern
-
-from synapse.api.errors import InteractiveAuthIncompleteError
-from synapse.api.urls import CLIENT_API_PREFIX
-from synapse.types import JsonDict
-
-logger = logging.getLogger(__name__)
-
-
-def client_patterns(
-    path_regex: str,
-    releases: Iterable[int] = (0,),
-    unstable: bool = True,
-    v1: bool = False,
-) -> Iterable[Pattern]:
-    """Creates a regex compiled client path with the correct client path
-    prefix.
-
-    Args:
-        path_regex: The regex string to match. This should NOT have a ^
-            as this will be prefixed.
-        releases: An iterable of releases to include this endpoint under.
-        unstable: If true, include this endpoint under the "unstable" prefix.
-        v1: If true, include this endpoint under the "api/v1" prefix.
-    Returns:
-        An iterable of patterns.
-    """
-    patterns = []
-
-    if unstable:
-        unstable_prefix = CLIENT_API_PREFIX + "/unstable"
-        patterns.append(re.compile("^" + unstable_prefix + path_regex))
-    if v1:
-        v1_prefix = CLIENT_API_PREFIX + "/api/v1"
-        patterns.append(re.compile("^" + v1_prefix + path_regex))
-    for release in releases:
-        new_prefix = CLIENT_API_PREFIX + "/r%d" % (release,)
-        patterns.append(re.compile("^" + new_prefix + path_regex))
-
-    return patterns
-
-
-def set_timeline_upper_limit(filter_json: JsonDict, filter_timeline_limit: int) -> None:
-    """
-    Enforces a maximum limit of a timeline query.
-
-    Params:
-        filter_json: The timeline query to modify.
-        filter_timeline_limit: The maximum limit to allow, passing -1 will
-            disable enforcing a maximum limit.
-    """
-    if filter_timeline_limit < 0:
-        return  # no upper limits
-    timeline = filter_json.get("room", {}).get("timeline", {})
-    if "limit" in timeline:
-        filter_json["room"]["timeline"]["limit"] = min(
-            filter_json["room"]["timeline"]["limit"], filter_timeline_limit
-        )
-
-
-def interactive_auth_handler(orig):
-    """Wraps an on_POST method to handle InteractiveAuthIncompleteErrors
-
-    Takes a on_POST method which returns an Awaitable (errcode, body) response
-    and adds exception handling to turn a InteractiveAuthIncompleteError into
-    a 401 response.
-
-    Normal usage is:
-
-    @interactive_auth_handler
-    async def on_POST(self, request):
-        # ...
-        await self.auth_handler.check_auth
-    """
-
-    async def wrapped(*args, **kwargs):
-        try:
-            return await orig(*args, **kwargs)
-        except InteractiveAuthIncompleteError as e:
-            return 401, e.result
-
-    return wrapped
diff --git a/synapse/rest/client/v2_alpha/account.py b/synapse/rest/client/v2_alpha/account.py
deleted file mode 100644
index fb5ad2906e..0000000000
--- a/synapse/rest/client/v2_alpha/account.py
+++ /dev/null
@@ -1,910 +0,0 @@
-# Copyright 2015, 2016 OpenMarket Ltd
-# Copyright 2017 Vector Creations Ltd
-# Copyright 2018 New Vector Ltd
-#
-# 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.
-import logging
-import random
-from http import HTTPStatus
-from typing import TYPE_CHECKING
-from urllib.parse import urlparse
-
-from synapse.api.constants import LoginType
-from synapse.api.errors import (
-    Codes,
-    InteractiveAuthIncompleteError,
-    SynapseError,
-    ThreepidValidationError,
-)
-from synapse.config.emailconfig import ThreepidBehaviour
-from synapse.handlers.ui_auth import UIAuthSessionDataConstants
-from synapse.http.server import finish_request, respond_with_html
-from synapse.http.servlet import (
-    RestServlet,
-    assert_params_in_dict,
-    parse_json_object_from_request,
-    parse_string,
-)
-from synapse.metrics import threepid_send_requests
-from synapse.push.mailer import Mailer
-from synapse.util.msisdn import phone_number_to_msisdn
-from synapse.util.stringutils import assert_valid_client_secret, random_string
-from synapse.util.threepids import check_3pid_allowed, validate_email
-
-from ._base import client_patterns, interactive_auth_handler
-
-if TYPE_CHECKING:
-    from synapse.server import HomeServer
-
-
-logger = logging.getLogger(__name__)
-
-
-class EmailPasswordRequestTokenRestServlet(RestServlet):
-    PATTERNS = client_patterns("/account/password/email/requestToken$")
-
-    def __init__(self, hs: "HomeServer"):
-        super().__init__()
-        self.hs = hs
-        self.datastore = hs.get_datastore()
-        self.config = hs.config
-        self.identity_handler = hs.get_identity_handler()
-
-        if self.config.threepid_behaviour_email == ThreepidBehaviour.LOCAL:
-            self.mailer = Mailer(
-                hs=self.hs,
-                app_name=self.config.email_app_name,
-                template_html=self.config.email_password_reset_template_html,
-                template_text=self.config.email_password_reset_template_text,
-            )
-
-    async def on_POST(self, request):
-        if self.config.threepid_behaviour_email == ThreepidBehaviour.OFF:
-            if self.config.local_threepid_handling_disabled_due_to_email_config:
-                logger.warning(
-                    "User password resets have been disabled due to lack of email config"
-                )
-            raise SynapseError(
-                400, "Email-based password resets have been disabled on this server"
-            )
-
-        body = parse_json_object_from_request(request)
-
-        assert_params_in_dict(body, ["client_secret", "email", "send_attempt"])
-
-        # Extract params from body
-        client_secret = body["client_secret"]
-        assert_valid_client_secret(client_secret)
-
-        # Canonicalise the email address. The addresses are all stored canonicalised
-        # in the database. This allows the user to reset his password without having to
-        # know the exact spelling (eg. upper and lower case) of address in the database.
-        # Stored in the database "foo@bar.com"
-        # User requests with "FOO@bar.com" would raise a Not Found error
-        try:
-            email = validate_email(body["email"])
-        except ValueError as e:
-            raise SynapseError(400, str(e))
-        send_attempt = body["send_attempt"]
-        next_link = body.get("next_link")  # Optional param
-
-        if next_link:
-            # Raise if the provided next_link value isn't valid
-            assert_valid_next_link(self.hs, next_link)
-
-        await self.identity_handler.ratelimit_request_token_requests(
-            request, "email", email
-        )
-
-        # The email will be sent to the stored address.
-        # This avoids a potential account hijack by requesting a password reset to
-        # an email address which is controlled by the attacker but which, after
-        # canonicalisation, matches the one in our database.
-        existing_user_id = await self.hs.get_datastore().get_user_id_by_threepid(
-            "email", email
-        )
-
-        if existing_user_id is None:
-            if self.config.request_token_inhibit_3pid_errors:
-                # Make the client think the operation succeeded. See the rationale in the
-                # comments for request_token_inhibit_3pid_errors.
-                # Also wait for some random amount of time between 100ms and 1s to make it
-                # look like we did something.
-                await self.hs.get_clock().sleep(random.randint(1, 10) / 10)
-                return 200, {"sid": random_string(16)}
-
-            raise SynapseError(400, "Email not found", Codes.THREEPID_NOT_FOUND)
-
-        if self.config.threepid_behaviour_email == ThreepidBehaviour.REMOTE:
-            assert self.hs.config.account_threepid_delegate_email
-
-            # Have the configured identity server handle the request
-            ret = await self.identity_handler.requestEmailToken(
-                self.hs.config.account_threepid_delegate_email,
-                email,
-                client_secret,
-                send_attempt,
-                next_link,
-            )
-        else:
-            # Send password reset emails from Synapse
-            sid = await self.identity_handler.send_threepid_validation(
-                email,
-                client_secret,
-                send_attempt,
-                self.mailer.send_password_reset_mail,
-                next_link,
-            )
-
-            # Wrap the session id in a JSON object
-            ret = {"sid": sid}
-
-        threepid_send_requests.labels(type="email", reason="password_reset").observe(
-            send_attempt
-        )
-
-        return 200, ret
-
-
-class PasswordRestServlet(RestServlet):
-    PATTERNS = client_patterns("/account/password$")
-
-    def __init__(self, hs):
-        super().__init__()
-        self.hs = hs
-        self.auth = hs.get_auth()
-        self.auth_handler = hs.get_auth_handler()
-        self.datastore = self.hs.get_datastore()
-        self.password_policy_handler = hs.get_password_policy_handler()
-        self._set_password_handler = hs.get_set_password_handler()
-
-    @interactive_auth_handler
-    async def on_POST(self, request):
-        body = parse_json_object_from_request(request)
-
-        # we do basic sanity checks here because the auth layer will store these
-        # in sessions. Pull out the new password provided to us.
-        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)
-
-        # 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.
-        #
-        # In the first case, we offer a couple of means of identifying
-        # themselves (email and msisdn, though it's unclear if msisdn actually
-        # works).
-        #
-        # In the second case, we require a password to confirm their identity.
-
-        if self.auth.has_access_token(request):
-            requester = await self.auth.get_user_by_req(request)
-            try:
-                params, session_id = await self.auth_handler.validate_user_via_ui_auth(
-                    requester,
-                    request,
-                    body,
-                    "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,
-                        UIAuthSessionDataConstants.PASSWORD_HASH,
-                        password_hash,
-                    )
-                raise
-            user_id = requester.user.to_string()
-        else:
-            requester = None
-            try:
-                result, params, session_id = await self.auth_handler.check_ui_auth(
-                    [[LoginType.EMAIL_IDENTITY]],
-                    request,
-                    body,
-                    "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,
-                        UIAuthSessionDataConstants.PASSWORD_HASH,
-                        password_hash,
-                    )
-                raise
-
-            if LoginType.EMAIL_IDENTITY in result:
-                threepid = result[LoginType.EMAIL_IDENTITY]
-                if "medium" not in threepid or "address" not in threepid:
-                    raise SynapseError(500, "Malformed threepid")
-                if threepid["medium"] == "email":
-                    # For emails, canonicalise the address.
-                    # We store all email addresses canonicalised in the DB.
-                    # (See add_threepid in synapse/handlers/auth.py)
-                    try:
-                        threepid["address"] = validate_email(threepid["address"])
-                    except ValueError as e:
-                        raise SynapseError(400, str(e))
-                # if using email, we must know about the email they're authing with!
-                threepid_user_id = await self.datastore.get_user_id_by_threepid(
-                    threepid["medium"], threepid["address"]
-                )
-                if not threepid_user_id:
-                    raise SynapseError(404, "Email address not found", Codes.NOT_FOUND)
-                user_id = threepid_user_id
-            else:
-                logger.error("Auth succeeded but no known type! %r", result.keys())
-                raise SynapseError(500, "", Codes.UNKNOWN)
-
-        # If we have a password in this request, prefer it. Otherwise, use the
-        # password hash from an earlier request.
-        if new_password:
-            password_hash = await self.auth_handler.hash(new_password)
-        elif session_id is not None:
-            password_hash = await self.auth_handler.get_session_data(
-                session_id, UIAuthSessionDataConstants.PASSWORD_HASH, None
-            )
-        else:
-            # UI validation was skipped, but the request did not include a new
-            # password.
-            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, password_hash, logout_devices, requester
-        )
-
-        return 200, {}
-
-
-class DeactivateAccountRestServlet(RestServlet):
-    PATTERNS = client_patterns("/account/deactivate$")
-
-    def __init__(self, hs):
-        super().__init__()
-        self.hs = hs
-        self.auth = hs.get_auth()
-        self.auth_handler = hs.get_auth_handler()
-        self._deactivate_account_handler = hs.get_deactivate_account_handler()
-
-    @interactive_auth_handler
-    async def on_POST(self, request):
-        body = parse_json_object_from_request(request)
-        erase = body.get("erase", False)
-        if not isinstance(erase, bool):
-            raise SynapseError(
-                HTTPStatus.BAD_REQUEST,
-                "Param 'erase' must be a boolean, if given",
-                Codes.BAD_JSON,
-            )
-
-        requester = await self.auth.get_user_by_req(request)
-
-        # allow ASes to deactivate their own users
-        if requester.app_service:
-            await self._deactivate_account_handler.deactivate_account(
-                requester.user.to_string(), erase, requester
-            )
-            return 200, {}
-
-        await self.auth_handler.validate_user_via_ui_auth(
-            requester,
-            request,
-            body,
-            "deactivate your account",
-        )
-        result = await self._deactivate_account_handler.deactivate_account(
-            requester.user.to_string(),
-            erase,
-            requester,
-            id_server=body.get("id_server"),
-        )
-        if result:
-            id_server_unbind_result = "success"
-        else:
-            id_server_unbind_result = "no-support"
-
-        return 200, {"id_server_unbind_result": id_server_unbind_result}
-
-
-class EmailThreepidRequestTokenRestServlet(RestServlet):
-    PATTERNS = client_patterns("/account/3pid/email/requestToken$")
-
-    def __init__(self, hs):
-        super().__init__()
-        self.hs = hs
-        self.config = hs.config
-        self.identity_handler = hs.get_identity_handler()
-        self.store = self.hs.get_datastore()
-
-        if self.config.threepid_behaviour_email == ThreepidBehaviour.LOCAL:
-            self.mailer = Mailer(
-                hs=self.hs,
-                app_name=self.config.email_app_name,
-                template_html=self.config.email_add_threepid_template_html,
-                template_text=self.config.email_add_threepid_template_text,
-            )
-
-    async def on_POST(self, request):
-        if self.config.threepid_behaviour_email == ThreepidBehaviour.OFF:
-            if self.config.local_threepid_handling_disabled_due_to_email_config:
-                logger.warning(
-                    "Adding emails have been disabled due to lack of an email config"
-                )
-            raise SynapseError(
-                400, "Adding an email to your account is disabled on this server"
-            )
-
-        body = parse_json_object_from_request(request)
-        assert_params_in_dict(body, ["client_secret", "email", "send_attempt"])
-        client_secret = body["client_secret"]
-        assert_valid_client_secret(client_secret)
-
-        # Canonicalise the email address. The addresses are all stored canonicalised
-        # in the database.
-        # This ensures that the validation email is sent to the canonicalised address
-        # as it will later be entered into the database.
-        # Otherwise the email will be sent to "FOO@bar.com" and stored as
-        # "foo@bar.com" in database.
-        try:
-            email = validate_email(body["email"])
-        except ValueError as e:
-            raise SynapseError(400, str(e))
-        send_attempt = body["send_attempt"]
-        next_link = body.get("next_link")  # Optional param
-
-        if not check_3pid_allowed(self.hs, "email", email):
-            raise SynapseError(
-                403,
-                "Your email domain is not authorized on this server",
-                Codes.THREEPID_DENIED,
-            )
-
-        await self.identity_handler.ratelimit_request_token_requests(
-            request, "email", email
-        )
-
-        if next_link:
-            # Raise if the provided next_link value isn't valid
-            assert_valid_next_link(self.hs, next_link)
-
-        existing_user_id = await self.store.get_user_id_by_threepid("email", email)
-
-        if existing_user_id is not None:
-            if self.config.request_token_inhibit_3pid_errors:
-                # Make the client think the operation succeeded. See the rationale in the
-                # comments for request_token_inhibit_3pid_errors.
-                # Also wait for some random amount of time between 100ms and 1s to make it
-                # look like we did something.
-                await self.hs.get_clock().sleep(random.randint(1, 10) / 10)
-                return 200, {"sid": random_string(16)}
-
-            raise SynapseError(400, "Email is already in use", Codes.THREEPID_IN_USE)
-
-        if self.config.threepid_behaviour_email == ThreepidBehaviour.REMOTE:
-            assert self.hs.config.account_threepid_delegate_email
-
-            # Have the configured identity server handle the request
-            ret = await self.identity_handler.requestEmailToken(
-                self.hs.config.account_threepid_delegate_email,
-                email,
-                client_secret,
-                send_attempt,
-                next_link,
-            )
-        else:
-            # Send threepid validation emails from Synapse
-            sid = await self.identity_handler.send_threepid_validation(
-                email,
-                client_secret,
-                send_attempt,
-                self.mailer.send_add_threepid_mail,
-                next_link,
-            )
-
-            # Wrap the session id in a JSON object
-            ret = {"sid": sid}
-
-        threepid_send_requests.labels(type="email", reason="add_threepid").observe(
-            send_attempt
-        )
-
-        return 200, ret
-
-
-class MsisdnThreepidRequestTokenRestServlet(RestServlet):
-    PATTERNS = client_patterns("/account/3pid/msisdn/requestToken$")
-
-    def __init__(self, hs: "HomeServer"):
-        self.hs = hs
-        super().__init__()
-        self.store = self.hs.get_datastore()
-        self.identity_handler = hs.get_identity_handler()
-
-    async def on_POST(self, request):
-        body = parse_json_object_from_request(request)
-        assert_params_in_dict(
-            body, ["client_secret", "country", "phone_number", "send_attempt"]
-        )
-        client_secret = body["client_secret"]
-        assert_valid_client_secret(client_secret)
-
-        country = body["country"]
-        phone_number = body["phone_number"]
-        send_attempt = body["send_attempt"]
-        next_link = body.get("next_link")  # Optional param
-
-        msisdn = phone_number_to_msisdn(country, phone_number)
-
-        if not check_3pid_allowed(self.hs, "msisdn", msisdn):
-            raise SynapseError(
-                403,
-                "Account phone numbers are not authorized on this server",
-                Codes.THREEPID_DENIED,
-            )
-
-        await self.identity_handler.ratelimit_request_token_requests(
-            request, "msisdn", msisdn
-        )
-
-        if next_link:
-            # Raise if the provided next_link value isn't valid
-            assert_valid_next_link(self.hs, next_link)
-
-        existing_user_id = await self.store.get_user_id_by_threepid("msisdn", msisdn)
-
-        if existing_user_id is not None:
-            if self.hs.config.request_token_inhibit_3pid_errors:
-                # Make the client think the operation succeeded. See the rationale in the
-                # comments for request_token_inhibit_3pid_errors.
-                # Also wait for some random amount of time between 100ms and 1s to make it
-                # look like we did something.
-                await self.hs.get_clock().sleep(random.randint(1, 10) / 10)
-                return 200, {"sid": random_string(16)}
-
-            raise SynapseError(400, "MSISDN is already in use", Codes.THREEPID_IN_USE)
-
-        if not self.hs.config.account_threepid_delegate_msisdn:
-            logger.warning(
-                "No upstream msisdn account_threepid_delegate configured on the server to "
-                "handle this request"
-            )
-            raise SynapseError(
-                400,
-                "Adding phone numbers to user account is not supported by this homeserver",
-            )
-
-        ret = await self.identity_handler.requestMsisdnToken(
-            self.hs.config.account_threepid_delegate_msisdn,
-            country,
-            phone_number,
-            client_secret,
-            send_attempt,
-            next_link,
-        )
-
-        threepid_send_requests.labels(type="msisdn", reason="add_threepid").observe(
-            send_attempt
-        )
-
-        return 200, ret
-
-
-class AddThreepidEmailSubmitTokenServlet(RestServlet):
-    """Handles 3PID validation token submission for adding an email to a user's account"""
-
-    PATTERNS = client_patterns(
-        "/add_threepid/email/submit_token$", releases=(), unstable=True
-    )
-
-    def __init__(self, hs):
-        """
-        Args:
-            hs (synapse.server.HomeServer): server
-        """
-        super().__init__()
-        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_add_threepid_template_failure_html
-            )
-
-    async def on_GET(self, request):
-        if self.config.threepid_behaviour_email == ThreepidBehaviour.OFF:
-            if self.config.local_threepid_handling_disabled_due_to_email_config:
-                logger.warning(
-                    "Adding emails have been disabled due to lack of an email config"
-                )
-            raise SynapseError(
-                400, "Adding an email to your account is disabled on this server"
-            )
-        elif self.config.threepid_behaviour_email == ThreepidBehaviour.REMOTE:
-            raise SynapseError(
-                400,
-                "This homeserver is not validating threepids. Use an identity server "
-                "instead.",
-            )
-
-        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:
-                request.setResponseCode(302)
-                request.setHeader("Location", next_link)
-                finish_request(request)
-                return None
-
-            # Otherwise show the success template
-            html = self.config.email_add_threepid_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 AddThreepidMsisdnSubmitTokenServlet(RestServlet):
-    """Handles 3PID validation token submission for adding a phone number to a user's
-    account
-    """
-
-    PATTERNS = client_patterns(
-        "/add_threepid/msisdn/submit_token$", releases=(), unstable=True
-    )
-
-    def __init__(self, hs):
-        """
-        Args:
-            hs (synapse.server.HomeServer): server
-        """
-        super().__init__()
-        self.config = hs.config
-        self.clock = hs.get_clock()
-        self.store = hs.get_datastore()
-        self.identity_handler = hs.get_identity_handler()
-
-    async def on_POST(self, request):
-        if not self.config.account_threepid_delegate_msisdn:
-            raise SynapseError(
-                400,
-                "This homeserver is not validating phone numbers. Use an identity server "
-                "instead.",
-            )
-
-        body = parse_json_object_from_request(request)
-        assert_params_in_dict(body, ["client_secret", "sid", "token"])
-        assert_valid_client_secret(body["client_secret"])
-
-        # Proxy submit_token request to msisdn threepid delegate
-        response = await self.identity_handler.proxy_msisdn_submit_token(
-            self.config.account_threepid_delegate_msisdn,
-            body["client_secret"],
-            body["sid"],
-            body["token"],
-        )
-        return 200, response
-
-
-class ThreepidRestServlet(RestServlet):
-    PATTERNS = client_patterns("/account/3pid$")
-
-    def __init__(self, hs):
-        super().__init__()
-        self.hs = hs
-        self.identity_handler = hs.get_identity_handler()
-        self.auth = hs.get_auth()
-        self.auth_handler = hs.get_auth_handler()
-        self.datastore = self.hs.get_datastore()
-
-    async def on_GET(self, request):
-        requester = await self.auth.get_user_by_req(request)
-
-        threepids = await self.datastore.user_get_threepids(requester.user.to_string())
-
-        return 200, {"threepids": threepids}
-
-    async def on_POST(self, request):
-        if not self.hs.config.enable_3pid_changes:
-            raise SynapseError(
-                400, "3PID changes are disabled on this server", Codes.FORBIDDEN
-            )
-
-        requester = await self.auth.get_user_by_req(request)
-        user_id = requester.user.to_string()
-        body = parse_json_object_from_request(request)
-
-        threepid_creds = body.get("threePidCreds") or body.get("three_pid_creds")
-        if threepid_creds is None:
-            raise SynapseError(
-                400, "Missing param three_pid_creds", Codes.MISSING_PARAM
-            )
-        assert_params_in_dict(threepid_creds, ["client_secret", "sid"])
-
-        sid = threepid_creds["sid"]
-        client_secret = threepid_creds["client_secret"]
-        assert_valid_client_secret(client_secret)
-
-        validation_session = await self.identity_handler.validate_threepid_session(
-            client_secret, sid
-        )
-        if validation_session:
-            await self.auth_handler.add_threepid(
-                user_id,
-                validation_session["medium"],
-                validation_session["address"],
-                validation_session["validated_at"],
-            )
-            return 200, {}
-
-        raise SynapseError(
-            400, "No validated 3pid session found", Codes.THREEPID_AUTH_FAILED
-        )
-
-
-class ThreepidAddRestServlet(RestServlet):
-    PATTERNS = client_patterns("/account/3pid/add$")
-
-    def __init__(self, hs):
-        super().__init__()
-        self.hs = hs
-        self.identity_handler = hs.get_identity_handler()
-        self.auth = hs.get_auth()
-        self.auth_handler = hs.get_auth_handler()
-
-    @interactive_auth_handler
-    async def on_POST(self, request):
-        if not self.hs.config.enable_3pid_changes:
-            raise SynapseError(
-                400, "3PID changes are disabled on this server", Codes.FORBIDDEN
-            )
-
-        requester = await self.auth.get_user_by_req(request)
-        user_id = requester.user.to_string()
-        body = parse_json_object_from_request(request)
-
-        assert_params_in_dict(body, ["client_secret", "sid"])
-        sid = body["sid"]
-        client_secret = body["client_secret"]
-        assert_valid_client_secret(client_secret)
-
-        await self.auth_handler.validate_user_via_ui_auth(
-            requester,
-            request,
-            body,
-            "add a third-party identifier to your account",
-        )
-
-        validation_session = await self.identity_handler.validate_threepid_session(
-            client_secret, sid
-        )
-        if validation_session:
-            await self.auth_handler.add_threepid(
-                user_id,
-                validation_session["medium"],
-                validation_session["address"],
-                validation_session["validated_at"],
-            )
-            return 200, {}
-
-        raise SynapseError(
-            400, "No validated 3pid session found", Codes.THREEPID_AUTH_FAILED
-        )
-
-
-class ThreepidBindRestServlet(RestServlet):
-    PATTERNS = client_patterns("/account/3pid/bind$")
-
-    def __init__(self, hs):
-        super().__init__()
-        self.hs = hs
-        self.identity_handler = hs.get_identity_handler()
-        self.auth = hs.get_auth()
-
-    async def on_POST(self, request):
-        body = parse_json_object_from_request(request)
-
-        assert_params_in_dict(body, ["id_server", "sid", "client_secret"])
-        id_server = body["id_server"]
-        sid = body["sid"]
-        id_access_token = body.get("id_access_token")  # optional
-        client_secret = body["client_secret"]
-        assert_valid_client_secret(client_secret)
-
-        requester = await self.auth.get_user_by_req(request)
-        user_id = requester.user.to_string()
-
-        await self.identity_handler.bind_threepid(
-            client_secret, sid, user_id, id_server, id_access_token
-        )
-
-        return 200, {}
-
-
-class ThreepidUnbindRestServlet(RestServlet):
-    PATTERNS = client_patterns("/account/3pid/unbind$")
-
-    def __init__(self, hs):
-        super().__init__()
-        self.hs = hs
-        self.identity_handler = hs.get_identity_handler()
-        self.auth = hs.get_auth()
-        self.datastore = self.hs.get_datastore()
-
-    async def on_POST(self, request):
-        """Unbind the given 3pid from a specific identity server, or identity servers that are
-        known to have this 3pid bound
-        """
-        requester = await self.auth.get_user_by_req(request)
-        body = parse_json_object_from_request(request)
-        assert_params_in_dict(body, ["medium", "address"])
-
-        medium = body.get("medium")
-        address = body.get("address")
-        id_server = body.get("id_server")
-
-        # Attempt to unbind the threepid from an identity server. If id_server is None, try to
-        # unbind from all identity servers this threepid has been added to in the past
-        result = await self.identity_handler.try_unbind_threepid(
-            requester.user.to_string(),
-            {"address": address, "medium": medium, "id_server": id_server},
-        )
-        return 200, {"id_server_unbind_result": "success" if result else "no-support"}
-
-
-class ThreepidDeleteRestServlet(RestServlet):
-    PATTERNS = client_patterns("/account/3pid/delete$")
-
-    def __init__(self, hs):
-        super().__init__()
-        self.hs = hs
-        self.auth = hs.get_auth()
-        self.auth_handler = hs.get_auth_handler()
-
-    async def on_POST(self, request):
-        if not self.hs.config.enable_3pid_changes:
-            raise SynapseError(
-                400, "3PID changes are disabled on this server", Codes.FORBIDDEN
-            )
-
-        body = parse_json_object_from_request(request)
-        assert_params_in_dict(body, ["medium", "address"])
-
-        requester = await self.auth.get_user_by_req(request)
-        user_id = requester.user.to_string()
-
-        try:
-            ret = await self.auth_handler.delete_threepid(
-                user_id, body["medium"], body["address"], body.get("id_server")
-            )
-        except Exception:
-            # NB. This endpoint should succeed if there is nothing to
-            # delete, so it should only throw if something is wrong
-            # that we ought to care about.
-            logger.exception("Failed to remove threepid")
-            raise SynapseError(500, "Failed to remove threepid")
-
-        if ret:
-            id_server_unbind_result = "success"
-        else:
-            id_server_unbind_result = "no-support"
-
-        return 200, {"id_server_unbind_result": id_server_unbind_result}
-
-
-def assert_valid_next_link(hs: "HomeServer", next_link: str):
-    """
-    Raises a SynapseError if a given next_link value is invalid
-
-    next_link is valid if the scheme is http(s) and the next_link.domain_whitelist config
-    option is either empty or contains a domain that matches the one in the given next_link
-
-    Args:
-        hs: The homeserver object
-        next_link: The next_link value given by the client
-
-    Raises:
-        SynapseError: If the next_link is invalid
-    """
-    valid = True
-
-    # Parse the contents of the URL
-    next_link_parsed = urlparse(next_link)
-
-    # Scheme must not point to the local drive
-    if next_link_parsed.scheme == "file":
-        valid = False
-
-    # If the domain whitelist is set, the domain must be in it
-    if (
-        valid
-        and hs.config.next_link_domain_whitelist is not None
-        and next_link_parsed.hostname not in hs.config.next_link_domain_whitelist
-    ):
-        valid = False
-
-    if not valid:
-        raise SynapseError(
-            400,
-            "'next_link' domain not included in whitelist, or not http(s)",
-            errcode=Codes.INVALID_PARAM,
-        )
-
-
-class WhoamiRestServlet(RestServlet):
-    PATTERNS = client_patterns("/account/whoami$")
-
-    def __init__(self, hs):
-        super().__init__()
-        self.auth = hs.get_auth()
-
-    async def on_GET(self, request):
-        requester = await self.auth.get_user_by_req(request)
-
-        response = {"user_id": requester.user.to_string()}
-
-        # Appservices and similar accounts do not have device IDs
-        # that we can report on, so exclude them for compliance.
-        if requester.device_id is not None:
-            response["device_id"] = requester.device_id
-
-        return 200, response
-
-
-def register_servlets(hs, http_server):
-    EmailPasswordRequestTokenRestServlet(hs).register(http_server)
-    PasswordRestServlet(hs).register(http_server)
-    DeactivateAccountRestServlet(hs).register(http_server)
-    EmailThreepidRequestTokenRestServlet(hs).register(http_server)
-    MsisdnThreepidRequestTokenRestServlet(hs).register(http_server)
-    AddThreepidEmailSubmitTokenServlet(hs).register(http_server)
-    AddThreepidMsisdnSubmitTokenServlet(hs).register(http_server)
-    ThreepidRestServlet(hs).register(http_server)
-    ThreepidAddRestServlet(hs).register(http_server)
-    ThreepidBindRestServlet(hs).register(http_server)
-    ThreepidUnbindRestServlet(hs).register(http_server)
-    ThreepidDeleteRestServlet(hs).register(http_server)
-    WhoamiRestServlet(hs).register(http_server)
diff --git a/synapse/rest/client/v2_alpha/account_data.py b/synapse/rest/client/v2_alpha/account_data.py
deleted file mode 100644
index 7517e9304e..0000000000
--- a/synapse/rest/client/v2_alpha/account_data.py
+++ /dev/null
@@ -1,122 +0,0 @@
-# Copyright 2015, 2016 OpenMarket Ltd
-#
-# 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.
-
-import logging
-
-from synapse.api.errors import AuthError, NotFoundError, SynapseError
-from synapse.http.servlet import RestServlet, parse_json_object_from_request
-
-from ._base import client_patterns
-
-logger = logging.getLogger(__name__)
-
-
-class AccountDataServlet(RestServlet):
-    """
-    PUT /user/{user_id}/account_data/{account_dataType} HTTP/1.1
-    GET /user/{user_id}/account_data/{account_dataType} HTTP/1.1
-    """
-
-    PATTERNS = client_patterns(
-        "/user/(?P<user_id>[^/]*)/account_data/(?P<account_data_type>[^/]*)"
-    )
-
-    def __init__(self, hs):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.store = hs.get_datastore()
-        self.handler = hs.get_account_data_handler()
-
-    async def on_PUT(self, request, user_id, account_data_type):
-        requester = await self.auth.get_user_by_req(request)
-        if user_id != requester.user.to_string():
-            raise AuthError(403, "Cannot add account data for other users.")
-
-        body = parse_json_object_from_request(request)
-
-        await self.handler.add_account_data_for_user(user_id, account_data_type, body)
-
-        return 200, {}
-
-    async def on_GET(self, request, user_id, account_data_type):
-        requester = await self.auth.get_user_by_req(request)
-        if user_id != requester.user.to_string():
-            raise AuthError(403, "Cannot get account data for other users.")
-
-        event = await self.store.get_global_account_data_by_type_for_user(
-            account_data_type, user_id
-        )
-
-        if event is None:
-            raise NotFoundError("Account data not found")
-
-        return 200, event
-
-
-class RoomAccountDataServlet(RestServlet):
-    """
-    PUT /user/{user_id}/rooms/{room_id}/account_data/{account_dataType} HTTP/1.1
-    GET /user/{user_id}/rooms/{room_id}/account_data/{account_dataType} HTTP/1.1
-    """
-
-    PATTERNS = client_patterns(
-        "/user/(?P<user_id>[^/]*)"
-        "/rooms/(?P<room_id>[^/]*)"
-        "/account_data/(?P<account_data_type>[^/]*)"
-    )
-
-    def __init__(self, hs):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.store = hs.get_datastore()
-        self.handler = hs.get_account_data_handler()
-
-    async def on_PUT(self, request, user_id, room_id, account_data_type):
-        requester = await self.auth.get_user_by_req(request)
-        if user_id != requester.user.to_string():
-            raise AuthError(403, "Cannot add account data for other users.")
-
-        body = parse_json_object_from_request(request)
-
-        if account_data_type == "m.fully_read":
-            raise SynapseError(
-                405,
-                "Cannot set m.fully_read through this API."
-                " Use /rooms/!roomId:server.name/read_markers",
-            )
-
-        await self.handler.add_account_data_to_room(
-            user_id, room_id, account_data_type, body
-        )
-
-        return 200, {}
-
-    async def on_GET(self, request, user_id, room_id, account_data_type):
-        requester = await self.auth.get_user_by_req(request)
-        if user_id != requester.user.to_string():
-            raise AuthError(403, "Cannot get account data for other users.")
-
-        event = await self.store.get_account_data_for_room_and_type(
-            user_id, room_id, account_data_type
-        )
-
-        if event is None:
-            raise NotFoundError("Room account data not found")
-
-        return 200, event
-
-
-def register_servlets(hs, http_server):
-    AccountDataServlet(hs).register(http_server)
-    RoomAccountDataServlet(hs).register(http_server)
diff --git a/synapse/rest/client/v2_alpha/account_validity.py b/synapse/rest/client/v2_alpha/account_validity.py
deleted file mode 100644
index 3ebe401861..0000000000
--- a/synapse/rest/client/v2_alpha/account_validity.py
+++ /dev/null
@@ -1,104 +0,0 @@
-# Copyright 2019 New Vector Ltd
-#
-# 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.
-
-import logging
-
-from synapse.api.errors import SynapseError
-from synapse.http.server import respond_with_html
-from synapse.http.servlet import RestServlet
-
-from ._base import client_patterns
-
-logger = logging.getLogger(__name__)
-
-
-class AccountValidityRenewServlet(RestServlet):
-    PATTERNS = client_patterns("/account_validity/renew$")
-
-    def __init__(self, hs):
-        """
-        Args:
-            hs (synapse.server.HomeServer): server
-        """
-        super().__init__()
-
-        self.hs = hs
-        self.account_activity_handler = hs.get_account_validity_handler()
-        self.auth = hs.get_auth()
-        self.account_renewed_template = (
-            hs.config.account_validity.account_validity_account_renewed_template
-        )
-        self.account_previously_renewed_template = (
-            hs.config.account_validity.account_validity_account_previously_renewed_template
-        )
-        self.invalid_token_template = (
-            hs.config.account_validity.account_validity_invalid_token_template
-        )
-
-    async def on_GET(self, request):
-        if b"token" not in request.args:
-            raise SynapseError(400, "Missing renewal token")
-        renewal_token = request.args[b"token"][0]
-
-        (
-            token_valid,
-            token_stale,
-            expiration_ts,
-        ) = await self.account_activity_handler.renew_account(
-            renewal_token.decode("utf8")
-        )
-
-        if token_valid:
-            status_code = 200
-            response = self.account_renewed_template.render(expiration_ts=expiration_ts)
-        elif token_stale:
-            status_code = 200
-            response = self.account_previously_renewed_template.render(
-                expiration_ts=expiration_ts
-            )
-        else:
-            status_code = 404
-            response = self.invalid_token_template.render(expiration_ts=expiration_ts)
-
-        respond_with_html(request, status_code, response)
-
-
-class AccountValiditySendMailServlet(RestServlet):
-    PATTERNS = client_patterns("/account_validity/send_mail$")
-
-    def __init__(self, hs):
-        """
-        Args:
-            hs (synapse.server.HomeServer): server
-        """
-        super().__init__()
-
-        self.hs = hs
-        self.account_activity_handler = hs.get_account_validity_handler()
-        self.auth = hs.get_auth()
-        self.account_validity_renew_by_email_enabled = (
-            hs.config.account_validity.account_validity_renew_by_email_enabled
-        )
-
-    async def on_POST(self, request):
-        requester = await self.auth.get_user_by_req(request, allow_expired=True)
-        user_id = requester.user.to_string()
-        await self.account_activity_handler.send_renewal_email_to_user(user_id)
-
-        return 200, {}
-
-
-def register_servlets(hs, http_server):
-    AccountValidityRenewServlet(hs).register(http_server)
-    AccountValiditySendMailServlet(hs).register(http_server)
diff --git a/synapse/rest/client/v2_alpha/auth.py b/synapse/rest/client/v2_alpha/auth.py
deleted file mode 100644
index 6ea1b50a62..0000000000
--- a/synapse/rest/client/v2_alpha/auth.py
+++ /dev/null
@@ -1,143 +0,0 @@
-# Copyright 2015, 2016 OpenMarket Ltd
-#
-# 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.
-
-import logging
-from typing import TYPE_CHECKING
-
-from synapse.api.constants import LoginType
-from synapse.api.errors import SynapseError
-from synapse.api.urls import CLIENT_API_PREFIX
-from synapse.http.server import respond_with_html
-from synapse.http.servlet import RestServlet, parse_string
-
-from ._base import client_patterns
-
-if TYPE_CHECKING:
-    from synapse.server import HomeServer
-
-logger = logging.getLogger(__name__)
-
-
-class AuthRestServlet(RestServlet):
-    """
-    Handles Client / Server API authentication in any situations where it
-    cannot be handled in the normal flow (with requests to the same endpoint).
-    Current use is for web fallback auth.
-    """
-
-    PATTERNS = client_patterns(r"/auth/(?P<stagetype>[\w\.]*)/fallback/web")
-
-    def __init__(self, hs: "HomeServer"):
-        super().__init__()
-        self.hs = hs
-        self.auth = hs.get_auth()
-        self.auth_handler = hs.get_auth_handler()
-        self.registration_handler = hs.get_registration_handler()
-        self.recaptcha_template = hs.config.recaptcha_template
-        self.terms_template = hs.config.terms_template
-        self.success_template = hs.config.fallback_success_template
-
-    async def on_GET(self, request, stagetype):
-        session = parse_string(request, "session")
-        if not session:
-            raise SynapseError(400, "No session supplied")
-
-        if stagetype == LoginType.RECAPTCHA:
-            html = self.recaptcha_template.render(
-                session=session,
-                myurl="%s/r0/auth/%s/fallback/web"
-                % (CLIENT_API_PREFIX, LoginType.RECAPTCHA),
-                sitekey=self.hs.config.recaptcha_public_key,
-            )
-        elif stagetype == LoginType.TERMS:
-            html = self.terms_template.render(
-                session=session,
-                terms_url="%s_matrix/consent?v=%s"
-                % (self.hs.config.public_baseurl, self.hs.config.user_consent_version),
-                myurl="%s/r0/auth/%s/fallback/web"
-                % (CLIENT_API_PREFIX, LoginType.TERMS),
-            )
-
-        elif stagetype == LoginType.SSO:
-            # Display a confirmation page which prompts the user to
-            # re-authenticate with their SSO provider.
-            html = await self.auth_handler.start_sso_ui_auth(request, session)
-
-        else:
-            raise SynapseError(404, "Unknown auth stage type")
-
-        # Render the HTML and return.
-        respond_with_html(request, 200, html)
-        return None
-
-    async def on_POST(self, request, stagetype):
-
-        session = parse_string(request, "session")
-        if not session:
-            raise SynapseError(400, "No session supplied")
-
-        if stagetype == LoginType.RECAPTCHA:
-            response = parse_string(request, "g-recaptcha-response")
-
-            if not response:
-                raise SynapseError(400, "No captcha response supplied")
-
-            authdict = {"response": response, "session": session}
-
-            success = await self.auth_handler.add_oob_auth(
-                LoginType.RECAPTCHA, authdict, request.getClientIP()
-            )
-
-            if success:
-                html = self.success_template.render()
-            else:
-                html = self.recaptcha_template.render(
-                    session=session,
-                    myurl="%s/r0/auth/%s/fallback/web"
-                    % (CLIENT_API_PREFIX, LoginType.RECAPTCHA),
-                    sitekey=self.hs.config.recaptcha_public_key,
-                )
-        elif stagetype == LoginType.TERMS:
-            authdict = {"session": session}
-
-            success = await self.auth_handler.add_oob_auth(
-                LoginType.TERMS, authdict, request.getClientIP()
-            )
-
-            if success:
-                html = self.success_template.render()
-            else:
-                html = self.terms_template.render(
-                    session=session,
-                    terms_url="%s_matrix/consent?v=%s"
-                    % (
-                        self.hs.config.public_baseurl,
-                        self.hs.config.user_consent_version,
-                    ),
-                    myurl="%s/r0/auth/%s/fallback/web"
-                    % (CLIENT_API_PREFIX, LoginType.TERMS),
-                )
-        elif stagetype == LoginType.SSO:
-            # The SSO fallback workflow should not post here,
-            raise SynapseError(404, "Fallback SSO auth does not support POST requests.")
-        else:
-            raise SynapseError(404, "Unknown auth stage type")
-
-        # Render the HTML and return.
-        respond_with_html(request, 200, html)
-        return None
-
-
-def register_servlets(hs, http_server):
-    AuthRestServlet(hs).register(http_server)
diff --git a/synapse/rest/client/v2_alpha/capabilities.py b/synapse/rest/client/v2_alpha/capabilities.py
deleted file mode 100644
index 88e3aac797..0000000000
--- a/synapse/rest/client/v2_alpha/capabilities.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# Copyright 2019 New Vector
-#
-# 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.
-import logging
-from typing import TYPE_CHECKING, Tuple
-
-from synapse.api.room_versions import KNOWN_ROOM_VERSIONS, MSC3244_CAPABILITIES
-from synapse.http.servlet import RestServlet
-from synapse.http.site import SynapseRequest
-from synapse.types import JsonDict
-
-from ._base import client_patterns
-
-if TYPE_CHECKING:
-    from synapse.server import HomeServer
-
-logger = logging.getLogger(__name__)
-
-
-class CapabilitiesRestServlet(RestServlet):
-    """End point to expose the capabilities of the server."""
-
-    PATTERNS = client_patterns("/capabilities$")
-
-    def __init__(self, hs: "HomeServer"):
-        super().__init__()
-        self.hs = hs
-        self.config = hs.config
-        self.auth = hs.get_auth()
-        self.auth_handler = hs.get_auth_handler()
-
-    async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
-        await self.auth.get_user_by_req(request, allow_guest=True)
-        change_password = self.auth_handler.can_change_password()
-
-        response = {
-            "capabilities": {
-                "m.room_versions": {
-                    "default": self.config.default_room_version.identifier,
-                    "available": {
-                        v.identifier: v.disposition
-                        for v in KNOWN_ROOM_VERSIONS.values()
-                    },
-                },
-                "m.change_password": {"enabled": change_password},
-            }
-        }
-
-        if self.config.experimental.msc3244_enabled:
-            response["capabilities"]["m.room_versions"][
-                "org.matrix.msc3244.room_capabilities"
-            ] = MSC3244_CAPABILITIES
-
-        return 200, response
-
-
-def register_servlets(hs: "HomeServer", http_server):
-    CapabilitiesRestServlet(hs).register(http_server)
diff --git a/synapse/rest/client/v2_alpha/devices.py b/synapse/rest/client/v2_alpha/devices.py
deleted file mode 100644
index 8b9674db06..0000000000
--- a/synapse/rest/client/v2_alpha/devices.py
+++ /dev/null
@@ -1,300 +0,0 @@
-# Copyright 2015, 2016 OpenMarket Ltd
-# 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.
-
-import logging
-
-from synapse.api import errors
-from synapse.http.servlet import (
-    RestServlet,
-    assert_params_in_dict,
-    parse_json_object_from_request,
-)
-from synapse.http.site import SynapseRequest
-
-from ._base import client_patterns, interactive_auth_handler
-
-logger = logging.getLogger(__name__)
-
-
-class DevicesRestServlet(RestServlet):
-    PATTERNS = client_patterns("/devices$")
-
-    def __init__(self, hs):
-        """
-        Args:
-            hs (synapse.server.HomeServer): server
-        """
-        super().__init__()
-        self.hs = hs
-        self.auth = hs.get_auth()
-        self.device_handler = hs.get_device_handler()
-
-    async def on_GET(self, request):
-        requester = await self.auth.get_user_by_req(request, allow_guest=True)
-        devices = await self.device_handler.get_devices_by_user(
-            requester.user.to_string()
-        )
-        return 200, {"devices": devices}
-
-
-class DeleteDevicesRestServlet(RestServlet):
-    """
-    API for bulk deletion of devices. Accepts a JSON object with a devices
-    key which lists the device_ids to delete. Requires user interactive auth.
-    """
-
-    PATTERNS = client_patterns("/delete_devices")
-
-    def __init__(self, hs):
-        super().__init__()
-        self.hs = hs
-        self.auth = hs.get_auth()
-        self.device_handler = hs.get_device_handler()
-        self.auth_handler = hs.get_auth_handler()
-
-    @interactive_auth_handler
-    async def on_POST(self, request):
-        requester = await self.auth.get_user_by_req(request)
-
-        try:
-            body = parse_json_object_from_request(request)
-        except errors.SynapseError as e:
-            if e.errcode == errors.Codes.NOT_JSON:
-                # DELETE
-                # deal with older clients which didn't pass a JSON dict
-                # the same as those that pass an empty dict
-                body = {}
-            else:
-                raise e
-
-        assert_params_in_dict(body, ["devices"])
-
-        await self.auth_handler.validate_user_via_ui_auth(
-            requester,
-            request,
-            body,
-            "remove device(s) from your account",
-            # Users might call this multiple times in a row while cleaning up
-            # devices, allow a single UI auth session to be re-used.
-            can_skip_ui_auth=True,
-        )
-
-        await self.device_handler.delete_devices(
-            requester.user.to_string(), body["devices"]
-        )
-        return 200, {}
-
-
-class DeviceRestServlet(RestServlet):
-    PATTERNS = client_patterns("/devices/(?P<device_id>[^/]*)$")
-
-    def __init__(self, hs):
-        """
-        Args:
-            hs (synapse.server.HomeServer): server
-        """
-        super().__init__()
-        self.hs = hs
-        self.auth = hs.get_auth()
-        self.device_handler = hs.get_device_handler()
-        self.auth_handler = hs.get_auth_handler()
-
-    async def on_GET(self, request, device_id):
-        requester = await self.auth.get_user_by_req(request, allow_guest=True)
-        device = await self.device_handler.get_device(
-            requester.user.to_string(), device_id
-        )
-        return 200, device
-
-    @interactive_auth_handler
-    async def on_DELETE(self, request, device_id):
-        requester = await self.auth.get_user_by_req(request)
-
-        try:
-            body = parse_json_object_from_request(request)
-
-        except errors.SynapseError as e:
-            if e.errcode == errors.Codes.NOT_JSON:
-                # deal with older clients which didn't pass a JSON dict
-                # the same as those that pass an empty dict
-                body = {}
-            else:
-                raise
-
-        await self.auth_handler.validate_user_via_ui_auth(
-            requester,
-            request,
-            body,
-            "remove a device from your account",
-            # Users might call this multiple times in a row while cleaning up
-            # devices, allow a single UI auth session to be re-used.
-            can_skip_ui_auth=True,
-        )
-
-        await self.device_handler.delete_device(requester.user.to_string(), device_id)
-        return 200, {}
-
-    async def on_PUT(self, request, device_id):
-        requester = await self.auth.get_user_by_req(request, allow_guest=True)
-
-        body = parse_json_object_from_request(request)
-        await self.device_handler.update_device(
-            requester.user.to_string(), device_id, body
-        )
-        return 200, {}
-
-
-class DehydratedDeviceServlet(RestServlet):
-    """Retrieve or store a dehydrated device.
-
-    GET /org.matrix.msc2697.v2/dehydrated_device
-
-    HTTP/1.1 200 OK
-    Content-Type: application/json
-
-    {
-      "device_id": "dehydrated_device_id",
-      "device_data": {
-        "algorithm": "org.matrix.msc2697.v1.dehydration.v1.olm",
-        "account": "dehydrated_device"
-      }
-    }
-
-    PUT /org.matrix.msc2697/dehydrated_device
-    Content-Type: application/json
-
-    {
-      "device_data": {
-        "algorithm": "org.matrix.msc2697.v1.dehydration.v1.olm",
-        "account": "dehydrated_device"
-      }
-    }
-
-    HTTP/1.1 200 OK
-    Content-Type: application/json
-
-    {
-      "device_id": "dehydrated_device_id"
-    }
-
-    """
-
-    PATTERNS = client_patterns("/org.matrix.msc2697.v2/dehydrated_device", releases=())
-
-    def __init__(self, hs):
-        super().__init__()
-        self.hs = hs
-        self.auth = hs.get_auth()
-        self.device_handler = hs.get_device_handler()
-
-    async def on_GET(self, request: SynapseRequest):
-        requester = await self.auth.get_user_by_req(request)
-        dehydrated_device = await self.device_handler.get_dehydrated_device(
-            requester.user.to_string()
-        )
-        if dehydrated_device is not None:
-            (device_id, device_data) = dehydrated_device
-            result = {"device_id": device_id, "device_data": device_data}
-            return (200, result)
-        else:
-            raise errors.NotFoundError("No dehydrated device available")
-
-    async def on_PUT(self, request: SynapseRequest):
-        submission = parse_json_object_from_request(request)
-        requester = await self.auth.get_user_by_req(request)
-
-        if "device_data" not in submission:
-            raise errors.SynapseError(
-                400,
-                "device_data missing",
-                errcode=errors.Codes.MISSING_PARAM,
-            )
-        elif not isinstance(submission["device_data"], dict):
-            raise errors.SynapseError(
-                400,
-                "device_data must be an object",
-                errcode=errors.Codes.INVALID_PARAM,
-            )
-
-        device_id = await self.device_handler.store_dehydrated_device(
-            requester.user.to_string(),
-            submission["device_data"],
-            submission.get("initial_device_display_name", None),
-        )
-        return 200, {"device_id": device_id}
-
-
-class ClaimDehydratedDeviceServlet(RestServlet):
-    """Claim a dehydrated device.
-
-    POST /org.matrix.msc2697.v2/dehydrated_device/claim
-    Content-Type: application/json
-
-    {
-      "device_id": "dehydrated_device_id"
-    }
-
-    HTTP/1.1 200 OK
-    Content-Type: application/json
-
-    {
-      "success": true,
-    }
-
-    """
-
-    PATTERNS = client_patterns(
-        "/org.matrix.msc2697.v2/dehydrated_device/claim", releases=()
-    )
-
-    def __init__(self, hs):
-        super().__init__()
-        self.hs = hs
-        self.auth = hs.get_auth()
-        self.device_handler = hs.get_device_handler()
-
-    async def on_POST(self, request: SynapseRequest):
-        requester = await self.auth.get_user_by_req(request)
-
-        submission = parse_json_object_from_request(request)
-
-        if "device_id" not in submission:
-            raise errors.SynapseError(
-                400,
-                "device_id missing",
-                errcode=errors.Codes.MISSING_PARAM,
-            )
-        elif not isinstance(submission["device_id"], str):
-            raise errors.SynapseError(
-                400,
-                "device_id must be a string",
-                errcode=errors.Codes.INVALID_PARAM,
-            )
-
-        result = await self.device_handler.rehydrate_device(
-            requester.user.to_string(),
-            self.auth.get_access_token_from_request(request),
-            submission["device_id"],
-        )
-
-        return (200, result)
-
-
-def register_servlets(hs, http_server):
-    DeleteDevicesRestServlet(hs).register(http_server)
-    DevicesRestServlet(hs).register(http_server)
-    DeviceRestServlet(hs).register(http_server)
-    DehydratedDeviceServlet(hs).register(http_server)
-    ClaimDehydratedDeviceServlet(hs).register(http_server)
diff --git a/synapse/rest/client/v2_alpha/filter.py b/synapse/rest/client/v2_alpha/filter.py
deleted file mode 100644
index 411667a9c8..0000000000
--- a/synapse/rest/client/v2_alpha/filter.py
+++ /dev/null
@@ -1,94 +0,0 @@
-# Copyright 2015, 2016 OpenMarket Ltd
-#
-# 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.
-
-import logging
-
-from synapse.api.errors import AuthError, NotFoundError, StoreError, SynapseError
-from synapse.http.servlet import RestServlet, parse_json_object_from_request
-from synapse.types import UserID
-
-from ._base import client_patterns, set_timeline_upper_limit
-
-logger = logging.getLogger(__name__)
-
-
-class GetFilterRestServlet(RestServlet):
-    PATTERNS = client_patterns("/user/(?P<user_id>[^/]*)/filter/(?P<filter_id>[^/]*)")
-
-    def __init__(self, hs):
-        super().__init__()
-        self.hs = hs
-        self.auth = hs.get_auth()
-        self.filtering = hs.get_filtering()
-
-    async def on_GET(self, request, user_id, filter_id):
-        target_user = UserID.from_string(user_id)
-        requester = await self.auth.get_user_by_req(request)
-
-        if target_user != requester.user:
-            raise AuthError(403, "Cannot get filters for other users")
-
-        if not self.hs.is_mine(target_user):
-            raise AuthError(403, "Can only get filters for local users")
-
-        try:
-            filter_id = int(filter_id)
-        except Exception:
-            raise SynapseError(400, "Invalid filter_id")
-
-        try:
-            filter_collection = await self.filtering.get_user_filter(
-                user_localpart=target_user.localpart, filter_id=filter_id
-            )
-        except StoreError as e:
-            if e.code != 404:
-                raise
-            raise NotFoundError("No such filter")
-
-        return 200, filter_collection.get_filter_json()
-
-
-class CreateFilterRestServlet(RestServlet):
-    PATTERNS = client_patterns("/user/(?P<user_id>[^/]*)/filter")
-
-    def __init__(self, hs):
-        super().__init__()
-        self.hs = hs
-        self.auth = hs.get_auth()
-        self.filtering = hs.get_filtering()
-
-    async def on_POST(self, request, user_id):
-
-        target_user = UserID.from_string(user_id)
-        requester = await self.auth.get_user_by_req(request)
-
-        if target_user != requester.user:
-            raise AuthError(403, "Cannot create filters for other users")
-
-        if not self.hs.is_mine(target_user):
-            raise AuthError(403, "Can only create filters for local users")
-
-        content = parse_json_object_from_request(request)
-        set_timeline_upper_limit(content, self.hs.config.filter_timeline_limit)
-
-        filter_id = await self.filtering.add_user_filter(
-            user_localpart=target_user.localpart, user_filter=content
-        )
-
-        return 200, {"filter_id": str(filter_id)}
-
-
-def register_servlets(hs, http_server):
-    GetFilterRestServlet(hs).register(http_server)
-    CreateFilterRestServlet(hs).register(http_server)
diff --git a/synapse/rest/client/v2_alpha/groups.py b/synapse/rest/client/v2_alpha/groups.py
deleted file mode 100644
index 6285680c00..0000000000
--- a/synapse/rest/client/v2_alpha/groups.py
+++ /dev/null
@@ -1,957 +0,0 @@
-# Copyright 2017 Vector Creations Ltd
-# Copyright 2018 New Vector Ltd
-#
-# 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.
-
-import logging
-from functools import wraps
-from typing import TYPE_CHECKING, Optional, Tuple
-
-from twisted.web.server import Request
-
-from synapse.api.constants import (
-    MAX_GROUP_CATEGORYID_LENGTH,
-    MAX_GROUP_ROLEID_LENGTH,
-    MAX_GROUPID_LENGTH,
-)
-from synapse.api.errors import Codes, SynapseError
-from synapse.handlers.groups_local import GroupsLocalHandler
-from synapse.http.servlet import (
-    RestServlet,
-    assert_params_in_dict,
-    parse_json_object_from_request,
-)
-from synapse.http.site import SynapseRequest
-from synapse.types import GroupID, JsonDict
-
-from ._base import client_patterns
-
-if TYPE_CHECKING:
-    from synapse.server import HomeServer
-
-logger = logging.getLogger(__name__)
-
-
-def _validate_group_id(f):
-    """Wrapper to validate the form of the group ID.
-
-    Can be applied to any on_FOO methods that accepts a group ID as a URL parameter.
-    """
-
-    @wraps(f)
-    def wrapper(self, request: Request, group_id: str, *args, **kwargs):
-        if not GroupID.is_valid(group_id):
-            raise SynapseError(400, "%s is not a legal group ID" % (group_id,))
-
-        return f(self, request, group_id, *args, **kwargs)
-
-    return wrapper
-
-
-class GroupServlet(RestServlet):
-    """Get the group profile"""
-
-    PATTERNS = client_patterns("/groups/(?P<group_id>[^/]*)/profile$")
-
-    def __init__(self, hs: "HomeServer"):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.clock = hs.get_clock()
-        self.groups_handler = hs.get_groups_local_handler()
-
-    @_validate_group_id
-    async def on_GET(
-        self, request: SynapseRequest, group_id: str
-    ) -> Tuple[int, JsonDict]:
-        requester = await self.auth.get_user_by_req(request, allow_guest=True)
-        requester_user_id = requester.user.to_string()
-
-        group_description = await self.groups_handler.get_group_profile(
-            group_id, requester_user_id
-        )
-
-        return 200, group_description
-
-    @_validate_group_id
-    async def on_POST(
-        self, request: SynapseRequest, group_id: str
-    ) -> Tuple[int, JsonDict]:
-        requester = await self.auth.get_user_by_req(request)
-        requester_user_id = requester.user.to_string()
-
-        content = parse_json_object_from_request(request)
-        assert_params_in_dict(
-            content, ("name", "avatar_url", "short_description", "long_description")
-        )
-        assert isinstance(
-            self.groups_handler, GroupsLocalHandler
-        ), "Workers cannot create group profiles."
-        await self.groups_handler.update_group_profile(
-            group_id, requester_user_id, content
-        )
-
-        return 200, {}
-
-
-class GroupSummaryServlet(RestServlet):
-    """Get the full group summary"""
-
-    PATTERNS = client_patterns("/groups/(?P<group_id>[^/]*)/summary$")
-
-    def __init__(self, hs: "HomeServer"):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.clock = hs.get_clock()
-        self.groups_handler = hs.get_groups_local_handler()
-
-    @_validate_group_id
-    async def on_GET(
-        self, request: SynapseRequest, group_id: str
-    ) -> Tuple[int, JsonDict]:
-        requester = await self.auth.get_user_by_req(request, allow_guest=True)
-        requester_user_id = requester.user.to_string()
-
-        get_group_summary = await self.groups_handler.get_group_summary(
-            group_id, requester_user_id
-        )
-
-        return 200, get_group_summary
-
-
-class GroupSummaryRoomsCatServlet(RestServlet):
-    """Update/delete a rooms entry in the summary.
-
-    Matches both:
-        - /groups/:group/summary/rooms/:room_id
-        - /groups/:group/summary/categories/:category/rooms/:room_id
-    """
-
-    PATTERNS = client_patterns(
-        "/groups/(?P<group_id>[^/]*)/summary"
-        "(/categories/(?P<category_id>[^/]+))?"
-        "/rooms/(?P<room_id>[^/]*)$"
-    )
-
-    def __init__(self, hs: "HomeServer"):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.clock = hs.get_clock()
-        self.groups_handler = hs.get_groups_local_handler()
-
-    @_validate_group_id
-    async def on_PUT(
-        self,
-        request: SynapseRequest,
-        group_id: str,
-        category_id: Optional[str],
-        room_id: str,
-    ):
-        requester = await self.auth.get_user_by_req(request)
-        requester_user_id = requester.user.to_string()
-
-        if category_id == "":
-            raise SynapseError(400, "category_id cannot be empty", Codes.INVALID_PARAM)
-
-        if category_id and len(category_id) > MAX_GROUP_CATEGORYID_LENGTH:
-            raise SynapseError(
-                400,
-                "category_id may not be longer than %s characters"
-                % (MAX_GROUP_CATEGORYID_LENGTH,),
-                Codes.INVALID_PARAM,
-            )
-
-        content = parse_json_object_from_request(request)
-        assert isinstance(
-            self.groups_handler, GroupsLocalHandler
-        ), "Workers cannot modify group summaries."
-        resp = await self.groups_handler.update_group_summary_room(
-            group_id,
-            requester_user_id,
-            room_id=room_id,
-            category_id=category_id,
-            content=content,
-        )
-
-        return 200, resp
-
-    @_validate_group_id
-    async def on_DELETE(
-        self, request: SynapseRequest, group_id: str, category_id: str, room_id: str
-    ):
-        requester = await self.auth.get_user_by_req(request)
-        requester_user_id = requester.user.to_string()
-
-        assert isinstance(
-            self.groups_handler, GroupsLocalHandler
-        ), "Workers cannot modify group profiles."
-        resp = await self.groups_handler.delete_group_summary_room(
-            group_id, requester_user_id, room_id=room_id, category_id=category_id
-        )
-
-        return 200, resp
-
-
-class GroupCategoryServlet(RestServlet):
-    """Get/add/update/delete a group category"""
-
-    PATTERNS = client_patterns(
-        "/groups/(?P<group_id>[^/]*)/categories/(?P<category_id>[^/]+)$"
-    )
-
-    def __init__(self, hs: "HomeServer"):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.clock = hs.get_clock()
-        self.groups_handler = hs.get_groups_local_handler()
-
-    @_validate_group_id
-    async def on_GET(
-        self, request: SynapseRequest, group_id: str, category_id: str
-    ) -> Tuple[int, JsonDict]:
-        requester = await self.auth.get_user_by_req(request, allow_guest=True)
-        requester_user_id = requester.user.to_string()
-
-        category = await self.groups_handler.get_group_category(
-            group_id, requester_user_id, category_id=category_id
-        )
-
-        return 200, category
-
-    @_validate_group_id
-    async def on_PUT(
-        self, request: SynapseRequest, group_id: str, category_id: str
-    ) -> Tuple[int, JsonDict]:
-        requester = await self.auth.get_user_by_req(request)
-        requester_user_id = requester.user.to_string()
-
-        if not category_id:
-            raise SynapseError(400, "category_id cannot be empty", Codes.INVALID_PARAM)
-
-        if len(category_id) > MAX_GROUP_CATEGORYID_LENGTH:
-            raise SynapseError(
-                400,
-                "category_id may not be longer than %s characters"
-                % (MAX_GROUP_CATEGORYID_LENGTH,),
-                Codes.INVALID_PARAM,
-            )
-
-        content = parse_json_object_from_request(request)
-        assert isinstance(
-            self.groups_handler, GroupsLocalHandler
-        ), "Workers cannot modify group categories."
-        resp = await self.groups_handler.update_group_category(
-            group_id, requester_user_id, category_id=category_id, content=content
-        )
-
-        return 200, resp
-
-    @_validate_group_id
-    async def on_DELETE(
-        self, request: SynapseRequest, group_id: str, category_id: str
-    ) -> Tuple[int, JsonDict]:
-        requester = await self.auth.get_user_by_req(request)
-        requester_user_id = requester.user.to_string()
-
-        assert isinstance(
-            self.groups_handler, GroupsLocalHandler
-        ), "Workers cannot modify group categories."
-        resp = await self.groups_handler.delete_group_category(
-            group_id, requester_user_id, category_id=category_id
-        )
-
-        return 200, resp
-
-
-class GroupCategoriesServlet(RestServlet):
-    """Get all group categories"""
-
-    PATTERNS = client_patterns("/groups/(?P<group_id>[^/]*)/categories/$")
-
-    def __init__(self, hs: "HomeServer"):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.clock = hs.get_clock()
-        self.groups_handler = hs.get_groups_local_handler()
-
-    @_validate_group_id
-    async def on_GET(
-        self, request: SynapseRequest, group_id: str
-    ) -> Tuple[int, JsonDict]:
-        requester = await self.auth.get_user_by_req(request, allow_guest=True)
-        requester_user_id = requester.user.to_string()
-
-        category = await self.groups_handler.get_group_categories(
-            group_id, requester_user_id
-        )
-
-        return 200, category
-
-
-class GroupRoleServlet(RestServlet):
-    """Get/add/update/delete a group role"""
-
-    PATTERNS = client_patterns("/groups/(?P<group_id>[^/]*)/roles/(?P<role_id>[^/]+)$")
-
-    def __init__(self, hs: "HomeServer"):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.clock = hs.get_clock()
-        self.groups_handler = hs.get_groups_local_handler()
-
-    @_validate_group_id
-    async def on_GET(
-        self, request: SynapseRequest, group_id: str, role_id: str
-    ) -> Tuple[int, JsonDict]:
-        requester = await self.auth.get_user_by_req(request, allow_guest=True)
-        requester_user_id = requester.user.to_string()
-
-        category = await self.groups_handler.get_group_role(
-            group_id, requester_user_id, role_id=role_id
-        )
-
-        return 200, category
-
-    @_validate_group_id
-    async def on_PUT(
-        self, request: SynapseRequest, group_id: str, role_id: str
-    ) -> Tuple[int, JsonDict]:
-        requester = await self.auth.get_user_by_req(request)
-        requester_user_id = requester.user.to_string()
-
-        if not role_id:
-            raise SynapseError(400, "role_id cannot be empty", Codes.INVALID_PARAM)
-
-        if len(role_id) > MAX_GROUP_ROLEID_LENGTH:
-            raise SynapseError(
-                400,
-                "role_id may not be longer than %s characters"
-                % (MAX_GROUP_ROLEID_LENGTH,),
-                Codes.INVALID_PARAM,
-            )
-
-        content = parse_json_object_from_request(request)
-        assert isinstance(
-            self.groups_handler, GroupsLocalHandler
-        ), "Workers cannot modify group roles."
-        resp = await self.groups_handler.update_group_role(
-            group_id, requester_user_id, role_id=role_id, content=content
-        )
-
-        return 200, resp
-
-    @_validate_group_id
-    async def on_DELETE(
-        self, request: SynapseRequest, group_id: str, role_id: str
-    ) -> Tuple[int, JsonDict]:
-        requester = await self.auth.get_user_by_req(request)
-        requester_user_id = requester.user.to_string()
-
-        assert isinstance(
-            self.groups_handler, GroupsLocalHandler
-        ), "Workers cannot modify group roles."
-        resp = await self.groups_handler.delete_group_role(
-            group_id, requester_user_id, role_id=role_id
-        )
-
-        return 200, resp
-
-
-class GroupRolesServlet(RestServlet):
-    """Get all group roles"""
-
-    PATTERNS = client_patterns("/groups/(?P<group_id>[^/]*)/roles/$")
-
-    def __init__(self, hs: "HomeServer"):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.clock = hs.get_clock()
-        self.groups_handler = hs.get_groups_local_handler()
-
-    @_validate_group_id
-    async def on_GET(
-        self, request: SynapseRequest, group_id: str
-    ) -> Tuple[int, JsonDict]:
-        requester = await self.auth.get_user_by_req(request, allow_guest=True)
-        requester_user_id = requester.user.to_string()
-
-        category = await self.groups_handler.get_group_roles(
-            group_id, requester_user_id
-        )
-
-        return 200, category
-
-
-class GroupSummaryUsersRoleServlet(RestServlet):
-    """Update/delete a user's entry in the summary.
-
-    Matches both:
-        - /groups/:group/summary/users/:room_id
-        - /groups/:group/summary/roles/:role/users/:user_id
-    """
-
-    PATTERNS = client_patterns(
-        "/groups/(?P<group_id>[^/]*)/summary"
-        "(/roles/(?P<role_id>[^/]+))?"
-        "/users/(?P<user_id>[^/]*)$"
-    )
-
-    def __init__(self, hs: "HomeServer"):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.clock = hs.get_clock()
-        self.groups_handler = hs.get_groups_local_handler()
-
-    @_validate_group_id
-    async def on_PUT(
-        self,
-        request: SynapseRequest,
-        group_id: str,
-        role_id: Optional[str],
-        user_id: str,
-    ) -> Tuple[int, JsonDict]:
-        requester = await self.auth.get_user_by_req(request)
-        requester_user_id = requester.user.to_string()
-
-        if role_id == "":
-            raise SynapseError(400, "role_id cannot be empty", Codes.INVALID_PARAM)
-
-        if role_id and len(role_id) > MAX_GROUP_ROLEID_LENGTH:
-            raise SynapseError(
-                400,
-                "role_id may not be longer than %s characters"
-                % (MAX_GROUP_ROLEID_LENGTH,),
-                Codes.INVALID_PARAM,
-            )
-
-        content = parse_json_object_from_request(request)
-        assert isinstance(
-            self.groups_handler, GroupsLocalHandler
-        ), "Workers cannot modify group summaries."
-        resp = await self.groups_handler.update_group_summary_user(
-            group_id,
-            requester_user_id,
-            user_id=user_id,
-            role_id=role_id,
-            content=content,
-        )
-
-        return 200, resp
-
-    @_validate_group_id
-    async def on_DELETE(
-        self, request: SynapseRequest, group_id: str, role_id: str, user_id: str
-    ):
-        requester = await self.auth.get_user_by_req(request)
-        requester_user_id = requester.user.to_string()
-
-        assert isinstance(
-            self.groups_handler, GroupsLocalHandler
-        ), "Workers cannot modify group summaries."
-        resp = await self.groups_handler.delete_group_summary_user(
-            group_id, requester_user_id, user_id=user_id, role_id=role_id
-        )
-
-        return 200, resp
-
-
-class GroupRoomServlet(RestServlet):
-    """Get all rooms in a group"""
-
-    PATTERNS = client_patterns("/groups/(?P<group_id>[^/]*)/rooms$")
-
-    def __init__(self, hs: "HomeServer"):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.clock = hs.get_clock()
-        self.groups_handler = hs.get_groups_local_handler()
-
-    @_validate_group_id
-    async def on_GET(
-        self, request: SynapseRequest, group_id: str
-    ) -> Tuple[int, JsonDict]:
-        requester = await self.auth.get_user_by_req(request, allow_guest=True)
-        requester_user_id = requester.user.to_string()
-
-        result = await self.groups_handler.get_rooms_in_group(
-            group_id, requester_user_id
-        )
-
-        return 200, result
-
-
-class GroupUsersServlet(RestServlet):
-    """Get all users in a group"""
-
-    PATTERNS = client_patterns("/groups/(?P<group_id>[^/]*)/users$")
-
-    def __init__(self, hs: "HomeServer"):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.clock = hs.get_clock()
-        self.groups_handler = hs.get_groups_local_handler()
-
-    @_validate_group_id
-    async def on_GET(
-        self, request: SynapseRequest, group_id: str
-    ) -> Tuple[int, JsonDict]:
-        requester = await self.auth.get_user_by_req(request, allow_guest=True)
-        requester_user_id = requester.user.to_string()
-
-        result = await self.groups_handler.get_users_in_group(
-            group_id, requester_user_id
-        )
-
-        return 200, result
-
-
-class GroupInvitedUsersServlet(RestServlet):
-    """Get users invited to a group"""
-
-    PATTERNS = client_patterns("/groups/(?P<group_id>[^/]*)/invited_users$")
-
-    def __init__(self, hs: "HomeServer"):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.clock = hs.get_clock()
-        self.groups_handler = hs.get_groups_local_handler()
-
-    @_validate_group_id
-    async def on_GET(
-        self, request: SynapseRequest, group_id: str
-    ) -> Tuple[int, JsonDict]:
-        requester = await self.auth.get_user_by_req(request)
-        requester_user_id = requester.user.to_string()
-
-        result = await self.groups_handler.get_invited_users_in_group(
-            group_id, requester_user_id
-        )
-
-        return 200, result
-
-
-class GroupSettingJoinPolicyServlet(RestServlet):
-    """Set group join policy"""
-
-    PATTERNS = client_patterns("/groups/(?P<group_id>[^/]*)/settings/m.join_policy$")
-
-    def __init__(self, hs: "HomeServer"):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.groups_handler = hs.get_groups_local_handler()
-
-    @_validate_group_id
-    async def on_PUT(
-        self, request: SynapseRequest, group_id: str
-    ) -> Tuple[int, JsonDict]:
-        requester = await self.auth.get_user_by_req(request)
-        requester_user_id = requester.user.to_string()
-
-        content = parse_json_object_from_request(request)
-
-        assert isinstance(
-            self.groups_handler, GroupsLocalHandler
-        ), "Workers cannot modify group join policy."
-        result = await self.groups_handler.set_group_join_policy(
-            group_id, requester_user_id, content
-        )
-
-        return 200, result
-
-
-class GroupCreateServlet(RestServlet):
-    """Create a group"""
-
-    PATTERNS = client_patterns("/create_group$")
-
-    def __init__(self, hs: "HomeServer"):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.clock = hs.get_clock()
-        self.groups_handler = hs.get_groups_local_handler()
-        self.server_name = hs.hostname
-
-    async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
-        requester = await self.auth.get_user_by_req(request)
-        requester_user_id = requester.user.to_string()
-
-        # TODO: Create group on remote server
-        content = parse_json_object_from_request(request)
-        localpart = content.pop("localpart")
-        group_id = GroupID(localpart, self.server_name).to_string()
-
-        if not localpart:
-            raise SynapseError(400, "Group ID cannot be empty", Codes.INVALID_PARAM)
-
-        if len(group_id) > MAX_GROUPID_LENGTH:
-            raise SynapseError(
-                400,
-                "Group ID may not be longer than %s characters" % (MAX_GROUPID_LENGTH,),
-                Codes.INVALID_PARAM,
-            )
-
-        assert isinstance(
-            self.groups_handler, GroupsLocalHandler
-        ), "Workers cannot create groups."
-        result = await self.groups_handler.create_group(
-            group_id, requester_user_id, content
-        )
-
-        return 200, result
-
-
-class GroupAdminRoomsServlet(RestServlet):
-    """Add a room to the group"""
-
-    PATTERNS = client_patterns(
-        "/groups/(?P<group_id>[^/]*)/admin/rooms/(?P<room_id>[^/]*)$"
-    )
-
-    def __init__(self, hs: "HomeServer"):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.clock = hs.get_clock()
-        self.groups_handler = hs.get_groups_local_handler()
-
-    @_validate_group_id
-    async def on_PUT(
-        self, request: SynapseRequest, group_id: str, room_id: str
-    ) -> Tuple[int, JsonDict]:
-        requester = await self.auth.get_user_by_req(request)
-        requester_user_id = requester.user.to_string()
-
-        content = parse_json_object_from_request(request)
-        assert isinstance(
-            self.groups_handler, GroupsLocalHandler
-        ), "Workers cannot modify rooms in a group."
-        result = await self.groups_handler.add_room_to_group(
-            group_id, requester_user_id, room_id, content
-        )
-
-        return 200, result
-
-    @_validate_group_id
-    async def on_DELETE(
-        self, request: SynapseRequest, group_id: str, room_id: str
-    ) -> Tuple[int, JsonDict]:
-        requester = await self.auth.get_user_by_req(request)
-        requester_user_id = requester.user.to_string()
-
-        assert isinstance(
-            self.groups_handler, GroupsLocalHandler
-        ), "Workers cannot modify group categories."
-        result = await self.groups_handler.remove_room_from_group(
-            group_id, requester_user_id, room_id
-        )
-
-        return 200, result
-
-
-class GroupAdminRoomsConfigServlet(RestServlet):
-    """Update the config of a room in a group"""
-
-    PATTERNS = client_patterns(
-        "/groups/(?P<group_id>[^/]*)/admin/rooms/(?P<room_id>[^/]*)"
-        "/config/(?P<config_key>[^/]*)$"
-    )
-
-    def __init__(self, hs: "HomeServer"):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.clock = hs.get_clock()
-        self.groups_handler = hs.get_groups_local_handler()
-
-    @_validate_group_id
-    async def on_PUT(
-        self, request: SynapseRequest, group_id: str, room_id: str, config_key: str
-    ):
-        requester = await self.auth.get_user_by_req(request)
-        requester_user_id = requester.user.to_string()
-
-        content = parse_json_object_from_request(request)
-        assert isinstance(
-            self.groups_handler, GroupsLocalHandler
-        ), "Workers cannot modify group categories."
-        result = await self.groups_handler.update_room_in_group(
-            group_id, requester_user_id, room_id, config_key, content
-        )
-
-        return 200, result
-
-
-class GroupAdminUsersInviteServlet(RestServlet):
-    """Invite a user to the group"""
-
-    PATTERNS = client_patterns(
-        "/groups/(?P<group_id>[^/]*)/admin/users/invite/(?P<user_id>[^/]*)$"
-    )
-
-    def __init__(self, hs: "HomeServer"):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.clock = hs.get_clock()
-        self.groups_handler = hs.get_groups_local_handler()
-        self.store = hs.get_datastore()
-        self.is_mine_id = hs.is_mine_id
-
-    @_validate_group_id
-    async def on_PUT(
-        self, request: SynapseRequest, group_id, user_id
-    ) -> Tuple[int, JsonDict]:
-        requester = await self.auth.get_user_by_req(request)
-        requester_user_id = requester.user.to_string()
-
-        content = parse_json_object_from_request(request)
-        config = content.get("config", {})
-        assert isinstance(
-            self.groups_handler, GroupsLocalHandler
-        ), "Workers cannot invite users to a group."
-        result = await self.groups_handler.invite(
-            group_id, user_id, requester_user_id, config
-        )
-
-        return 200, result
-
-
-class GroupAdminUsersKickServlet(RestServlet):
-    """Kick a user from the group"""
-
-    PATTERNS = client_patterns(
-        "/groups/(?P<group_id>[^/]*)/admin/users/remove/(?P<user_id>[^/]*)$"
-    )
-
-    def __init__(self, hs: "HomeServer"):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.clock = hs.get_clock()
-        self.groups_handler = hs.get_groups_local_handler()
-
-    @_validate_group_id
-    async def on_PUT(
-        self, request: SynapseRequest, group_id, user_id
-    ) -> Tuple[int, JsonDict]:
-        requester = await self.auth.get_user_by_req(request)
-        requester_user_id = requester.user.to_string()
-
-        content = parse_json_object_from_request(request)
-        assert isinstance(
-            self.groups_handler, GroupsLocalHandler
-        ), "Workers cannot kick users from a group."
-        result = await self.groups_handler.remove_user_from_group(
-            group_id, user_id, requester_user_id, content
-        )
-
-        return 200, result
-
-
-class GroupSelfLeaveServlet(RestServlet):
-    """Leave a joined group"""
-
-    PATTERNS = client_patterns("/groups/(?P<group_id>[^/]*)/self/leave$")
-
-    def __init__(self, hs: "HomeServer"):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.clock = hs.get_clock()
-        self.groups_handler = hs.get_groups_local_handler()
-
-    @_validate_group_id
-    async def on_PUT(
-        self, request: SynapseRequest, group_id: str
-    ) -> Tuple[int, JsonDict]:
-        requester = await self.auth.get_user_by_req(request)
-        requester_user_id = requester.user.to_string()
-
-        content = parse_json_object_from_request(request)
-        assert isinstance(
-            self.groups_handler, GroupsLocalHandler
-        ), "Workers cannot leave a group for a users."
-        result = await self.groups_handler.remove_user_from_group(
-            group_id, requester_user_id, requester_user_id, content
-        )
-
-        return 200, result
-
-
-class GroupSelfJoinServlet(RestServlet):
-    """Attempt to join a group, or knock"""
-
-    PATTERNS = client_patterns("/groups/(?P<group_id>[^/]*)/self/join$")
-
-    def __init__(self, hs: "HomeServer"):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.clock = hs.get_clock()
-        self.groups_handler = hs.get_groups_local_handler()
-
-    @_validate_group_id
-    async def on_PUT(
-        self, request: SynapseRequest, group_id: str
-    ) -> Tuple[int, JsonDict]:
-        requester = await self.auth.get_user_by_req(request)
-        requester_user_id = requester.user.to_string()
-
-        content = parse_json_object_from_request(request)
-        assert isinstance(
-            self.groups_handler, GroupsLocalHandler
-        ), "Workers cannot join a user to a group."
-        result = await self.groups_handler.join_group(
-            group_id, requester_user_id, content
-        )
-
-        return 200, result
-
-
-class GroupSelfAcceptInviteServlet(RestServlet):
-    """Accept a group invite"""
-
-    PATTERNS = client_patterns("/groups/(?P<group_id>[^/]*)/self/accept_invite$")
-
-    def __init__(self, hs: "HomeServer"):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.clock = hs.get_clock()
-        self.groups_handler = hs.get_groups_local_handler()
-
-    @_validate_group_id
-    async def on_PUT(
-        self, request: SynapseRequest, group_id: str
-    ) -> Tuple[int, JsonDict]:
-        requester = await self.auth.get_user_by_req(request)
-        requester_user_id = requester.user.to_string()
-
-        content = parse_json_object_from_request(request)
-        assert isinstance(
-            self.groups_handler, GroupsLocalHandler
-        ), "Workers cannot accept an invite to a group."
-        result = await self.groups_handler.accept_invite(
-            group_id, requester_user_id, content
-        )
-
-        return 200, result
-
-
-class GroupSelfUpdatePublicityServlet(RestServlet):
-    """Update whether we publicise a users membership of a group"""
-
-    PATTERNS = client_patterns("/groups/(?P<group_id>[^/]*)/self/update_publicity$")
-
-    def __init__(self, hs: "HomeServer"):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.clock = hs.get_clock()
-        self.store = hs.get_datastore()
-
-    @_validate_group_id
-    async def on_PUT(
-        self, request: SynapseRequest, group_id: str
-    ) -> Tuple[int, JsonDict]:
-        requester = await self.auth.get_user_by_req(request)
-        requester_user_id = requester.user.to_string()
-
-        content = parse_json_object_from_request(request)
-        publicise = content["publicise"]
-        await self.store.update_group_publicity(group_id, requester_user_id, publicise)
-
-        return 200, {}
-
-
-class PublicisedGroupsForUserServlet(RestServlet):
-    """Get the list of groups a user is advertising"""
-
-    PATTERNS = client_patterns("/publicised_groups/(?P<user_id>[^/]*)$")
-
-    def __init__(self, hs: "HomeServer"):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.clock = hs.get_clock()
-        self.store = hs.get_datastore()
-        self.groups_handler = hs.get_groups_local_handler()
-
-    async def on_GET(
-        self, request: SynapseRequest, user_id: str
-    ) -> Tuple[int, JsonDict]:
-        await self.auth.get_user_by_req(request, allow_guest=True)
-
-        result = await self.groups_handler.get_publicised_groups_for_user(user_id)
-
-        return 200, result
-
-
-class PublicisedGroupsForUsersServlet(RestServlet):
-    """Get the list of groups a user is advertising"""
-
-    PATTERNS = client_patterns("/publicised_groups$")
-
-    def __init__(self, hs: "HomeServer"):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.clock = hs.get_clock()
-        self.store = hs.get_datastore()
-        self.groups_handler = hs.get_groups_local_handler()
-
-    async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
-        await self.auth.get_user_by_req(request, allow_guest=True)
-
-        content = parse_json_object_from_request(request)
-        user_ids = content["user_ids"]
-
-        result = await self.groups_handler.bulk_get_publicised_groups(user_ids)
-
-        return 200, result
-
-
-class GroupsForUserServlet(RestServlet):
-    """Get all groups the logged in user is joined to"""
-
-    PATTERNS = client_patterns("/joined_groups$")
-
-    def __init__(self, hs: "HomeServer"):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.clock = hs.get_clock()
-        self.groups_handler = hs.get_groups_local_handler()
-
-    async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
-        requester = await self.auth.get_user_by_req(request, allow_guest=True)
-        requester_user_id = requester.user.to_string()
-
-        result = await self.groups_handler.get_joined_groups(requester_user_id)
-
-        return 200, result
-
-
-def register_servlets(hs: "HomeServer", http_server):
-    GroupServlet(hs).register(http_server)
-    GroupSummaryServlet(hs).register(http_server)
-    GroupInvitedUsersServlet(hs).register(http_server)
-    GroupUsersServlet(hs).register(http_server)
-    GroupRoomServlet(hs).register(http_server)
-    GroupSettingJoinPolicyServlet(hs).register(http_server)
-    GroupCreateServlet(hs).register(http_server)
-    GroupAdminRoomsServlet(hs).register(http_server)
-    GroupAdminRoomsConfigServlet(hs).register(http_server)
-    GroupAdminUsersInviteServlet(hs).register(http_server)
-    GroupAdminUsersKickServlet(hs).register(http_server)
-    GroupSelfLeaveServlet(hs).register(http_server)
-    GroupSelfJoinServlet(hs).register(http_server)
-    GroupSelfAcceptInviteServlet(hs).register(http_server)
-    GroupsForUserServlet(hs).register(http_server)
-    GroupCategoryServlet(hs).register(http_server)
-    GroupCategoriesServlet(hs).register(http_server)
-    GroupSummaryRoomsCatServlet(hs).register(http_server)
-    GroupRoleServlet(hs).register(http_server)
-    GroupRolesServlet(hs).register(http_server)
-    GroupSelfUpdatePublicityServlet(hs).register(http_server)
-    GroupSummaryUsersRoleServlet(hs).register(http_server)
-    PublicisedGroupsForUserServlet(hs).register(http_server)
-    PublicisedGroupsForUsersServlet(hs).register(http_server)
diff --git a/synapse/rest/client/v2_alpha/keys.py b/synapse/rest/client/v2_alpha/keys.py
deleted file mode 100644
index d0d9d30d40..0000000000
--- a/synapse/rest/client/v2_alpha/keys.py
+++ /dev/null
@@ -1,344 +0,0 @@
-# Copyright 2015, 2016 OpenMarket Ltd
-# Copyright 2019 New Vector Ltd
-# 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.
-
-import logging
-
-from synapse.api.errors import SynapseError
-from synapse.http.servlet import (
-    RestServlet,
-    parse_integer,
-    parse_json_object_from_request,
-    parse_string,
-)
-from synapse.logging.opentracing import log_kv, set_tag, trace
-from synapse.types import StreamToken
-
-from ._base import client_patterns, interactive_auth_handler
-
-logger = logging.getLogger(__name__)
-
-
-class KeyUploadServlet(RestServlet):
-    """
-    POST /keys/upload HTTP/1.1
-    Content-Type: application/json
-
-    {
-      "device_keys": {
-        "user_id": "<user_id>",
-        "device_id": "<device_id>",
-        "valid_until_ts": <millisecond_timestamp>,
-        "algorithms": [
-          "m.olm.curve25519-aes-sha2",
-        ]
-        "keys": {
-          "<algorithm>:<device_id>": "<key_base64>",
-        },
-        "signatures:" {
-          "<user_id>" {
-            "<algorithm>:<device_id>": "<signature_base64>"
-      } } },
-      "one_time_keys": {
-        "<algorithm>:<key_id>": "<key_base64>"
-      },
-    }
-    """
-
-    PATTERNS = client_patterns("/keys/upload(/(?P<device_id>[^/]+))?$")
-
-    def __init__(self, hs):
-        """
-        Args:
-            hs (synapse.server.HomeServer): server
-        """
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.e2e_keys_handler = hs.get_e2e_keys_handler()
-        self.device_handler = hs.get_device_handler()
-
-    @trace(opname="upload_keys")
-    async def on_POST(self, request, device_id):
-        requester = await self.auth.get_user_by_req(request, allow_guest=True)
-        user_id = requester.user.to_string()
-        body = parse_json_object_from_request(request)
-
-        if device_id is not None:
-            # Providing the device_id should only be done for setting keys
-            # for dehydrated devices; however, we allow it for any device for
-            # compatibility with older clients.
-            if requester.device_id is not None and device_id != requester.device_id:
-                dehydrated_device = await self.device_handler.get_dehydrated_device(
-                    user_id
-                )
-                if dehydrated_device is not None and device_id != dehydrated_device[0]:
-                    set_tag("error", True)
-                    log_kv(
-                        {
-                            "message": "Client uploading keys for a different device",
-                            "logged_in_id": requester.device_id,
-                            "key_being_uploaded": device_id,
-                        }
-                    )
-                    logger.warning(
-                        "Client uploading keys for a different device "
-                        "(logged in as %s, uploading for %s)",
-                        requester.device_id,
-                        device_id,
-                    )
-        else:
-            device_id = requester.device_id
-
-        if device_id is None:
-            raise SynapseError(
-                400, "To upload keys, you must pass device_id when authenticating"
-            )
-
-        result = await self.e2e_keys_handler.upload_keys_for_user(
-            user_id, device_id, body
-        )
-        return 200, result
-
-
-class KeyQueryServlet(RestServlet):
-    """
-    POST /keys/query HTTP/1.1
-    Content-Type: application/json
-    {
-      "device_keys": {
-        "<user_id>": ["<device_id>"]
-    } }
-
-    HTTP/1.1 200 OK
-    {
-      "device_keys": {
-        "<user_id>": {
-          "<device_id>": {
-            "user_id": "<user_id>", // Duplicated to be signed
-            "device_id": "<device_id>", // Duplicated to be signed
-            "valid_until_ts": <millisecond_timestamp>,
-            "algorithms": [ // List of supported algorithms
-              "m.olm.curve25519-aes-sha2",
-            ],
-            "keys": { // Must include a ed25519 signing key
-              "<algorithm>:<key_id>": "<key_base64>",
-            },
-            "signatures:" {
-              // Must be signed with device's ed25519 key
-              "<user_id>/<device_id>": {
-                "<algorithm>:<key_id>": "<signature_base64>"
-              }
-              // Must be signed by this server.
-              "<server_name>": {
-                "<algorithm>:<key_id>": "<signature_base64>"
-    } } } } } }
-    """
-
-    PATTERNS = client_patterns("/keys/query$")
-
-    def __init__(self, hs):
-        """
-        Args:
-            hs (synapse.server.HomeServer):
-        """
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.e2e_keys_handler = hs.get_e2e_keys_handler()
-
-    async def on_POST(self, request):
-        requester = await self.auth.get_user_by_req(request, allow_guest=True)
-        user_id = requester.user.to_string()
-        device_id = requester.device_id
-        timeout = parse_integer(request, "timeout", 10 * 1000)
-        body = parse_json_object_from_request(request)
-        result = await self.e2e_keys_handler.query_devices(
-            body, timeout, user_id, device_id
-        )
-        return 200, result
-
-
-class KeyChangesServlet(RestServlet):
-    """Returns the list of changes of keys between two stream tokens (may return
-    spurious extra results, since we currently ignore the `to` param).
-
-        GET /keys/changes?from=...&to=...
-
-        200 OK
-        { "changed": ["@foo:example.com"] }
-    """
-
-    PATTERNS = client_patterns("/keys/changes$")
-
-    def __init__(self, hs):
-        """
-        Args:
-            hs (synapse.server.HomeServer):
-        """
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.device_handler = hs.get_device_handler()
-        self.store = hs.get_datastore()
-
-    async def on_GET(self, request):
-        requester = await self.auth.get_user_by_req(request, allow_guest=True)
-
-        from_token_string = parse_string(request, "from", required=True)
-        set_tag("from", from_token_string)
-
-        # We want to enforce they do pass us one, but we ignore it and return
-        # changes after the "to" as well as before.
-        set_tag("to", parse_string(request, "to"))
-
-        from_token = await StreamToken.from_string(self.store, from_token_string)
-
-        user_id = requester.user.to_string()
-
-        results = await self.device_handler.get_user_ids_changed(user_id, from_token)
-
-        return 200, results
-
-
-class OneTimeKeyServlet(RestServlet):
-    """
-    POST /keys/claim HTTP/1.1
-    {
-      "one_time_keys": {
-        "<user_id>": {
-          "<device_id>": "<algorithm>"
-    } } }
-
-    HTTP/1.1 200 OK
-    {
-      "one_time_keys": {
-        "<user_id>": {
-          "<device_id>": {
-            "<algorithm>:<key_id>": "<key_base64>"
-    } } } }
-
-    """
-
-    PATTERNS = client_patterns("/keys/claim$")
-
-    def __init__(self, hs):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.e2e_keys_handler = hs.get_e2e_keys_handler()
-
-    async def on_POST(self, request):
-        await self.auth.get_user_by_req(request, allow_guest=True)
-        timeout = parse_integer(request, "timeout", 10 * 1000)
-        body = parse_json_object_from_request(request)
-        result = await self.e2e_keys_handler.claim_one_time_keys(body, timeout)
-        return 200, result
-
-
-class SigningKeyUploadServlet(RestServlet):
-    """
-    POST /keys/device_signing/upload HTTP/1.1
-    Content-Type: application/json
-
-    {
-    }
-    """
-
-    PATTERNS = client_patterns("/keys/device_signing/upload$", releases=())
-
-    def __init__(self, hs):
-        """
-        Args:
-            hs (synapse.server.HomeServer): server
-        """
-        super().__init__()
-        self.hs = hs
-        self.auth = hs.get_auth()
-        self.e2e_keys_handler = hs.get_e2e_keys_handler()
-        self.auth_handler = hs.get_auth_handler()
-
-    @interactive_auth_handler
-    async def on_POST(self, request):
-        requester = await self.auth.get_user_by_req(request)
-        user_id = requester.user.to_string()
-        body = parse_json_object_from_request(request)
-
-        await self.auth_handler.validate_user_via_ui_auth(
-            requester,
-            request,
-            body,
-            "add a device signing key to your account",
-            # Allow skipping of UI auth since this is frequently called directly
-            # after login and it is silly to ask users to re-auth immediately.
-            can_skip_ui_auth=True,
-        )
-
-        result = await self.e2e_keys_handler.upload_signing_keys_for_user(user_id, body)
-        return 200, result
-
-
-class SignaturesUploadServlet(RestServlet):
-    """
-    POST /keys/signatures/upload HTTP/1.1
-    Content-Type: application/json
-
-    {
-      "@alice:example.com": {
-        "<device_id>": {
-          "user_id": "<user_id>",
-          "device_id": "<device_id>",
-          "algorithms": [
-            "m.olm.curve25519-aes-sha2",
-            "m.megolm.v1.aes-sha2"
-          ],
-          "keys": {
-            "<algorithm>:<device_id>": "<key_base64>",
-          },
-          "signatures": {
-            "<signing_user_id>": {
-              "<algorithm>:<signing_key_base64>": "<signature_base64>>"
-            }
-          }
-        }
-      }
-    }
-    """
-
-    PATTERNS = client_patterns("/keys/signatures/upload$")
-
-    def __init__(self, hs):
-        """
-        Args:
-            hs (synapse.server.HomeServer): server
-        """
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.e2e_keys_handler = hs.get_e2e_keys_handler()
-
-    async def on_POST(self, request):
-        requester = await self.auth.get_user_by_req(request, allow_guest=True)
-        user_id = requester.user.to_string()
-        body = parse_json_object_from_request(request)
-
-        result = await self.e2e_keys_handler.upload_signatures_for_device_keys(
-            user_id, body
-        )
-        return 200, result
-
-
-def register_servlets(hs, http_server):
-    KeyUploadServlet(hs).register(http_server)
-    KeyQueryServlet(hs).register(http_server)
-    KeyChangesServlet(hs).register(http_server)
-    OneTimeKeyServlet(hs).register(http_server)
-    SigningKeyUploadServlet(hs).register(http_server)
-    SignaturesUploadServlet(hs).register(http_server)
diff --git a/synapse/rest/client/v2_alpha/knock.py b/synapse/rest/client/v2_alpha/knock.py
deleted file mode 100644
index 7d1bc40658..0000000000
--- a/synapse/rest/client/v2_alpha/knock.py
+++ /dev/null
@@ -1,107 +0,0 @@
-# Copyright 2020 Sorunome
-# 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.
-import logging
-from typing import TYPE_CHECKING, Dict, List, Optional, Tuple
-
-from twisted.web.server import Request
-
-from synapse.api.constants import Membership
-from synapse.api.errors import SynapseError
-from synapse.http.servlet import (
-    RestServlet,
-    parse_json_object_from_request,
-    parse_strings_from_args,
-)
-from synapse.http.site import SynapseRequest
-from synapse.logging.opentracing import set_tag
-from synapse.rest.client.transactions import HttpTransactionCache
-from synapse.types import JsonDict, RoomAlias, RoomID
-
-if TYPE_CHECKING:
-    from synapse.app.homeserver import HomeServer
-
-from ._base import client_patterns
-
-logger = logging.getLogger(__name__)
-
-
-class KnockRoomAliasServlet(RestServlet):
-    """
-    POST /knock/{roomIdOrAlias}
-    """
-
-    PATTERNS = client_patterns("/knock/(?P<room_identifier>[^/]*)")
-
-    def __init__(self, hs: "HomeServer"):
-        super().__init__()
-        self.txns = HttpTransactionCache(hs)
-        self.room_member_handler = hs.get_room_member_handler()
-        self.auth = hs.get_auth()
-
-    async def on_POST(
-        self,
-        request: SynapseRequest,
-        room_identifier: str,
-        txn_id: Optional[str] = None,
-    ) -> Tuple[int, JsonDict]:
-        requester = await self.auth.get_user_by_req(request)
-
-        content = parse_json_object_from_request(request)
-        event_content = None
-        if "reason" in content:
-            event_content = {"reason": content["reason"]}
-
-        if RoomID.is_valid(room_identifier):
-            room_id = room_identifier
-
-            # twisted.web.server.Request.args is incorrectly defined as Optional[Any]
-            args: Dict[bytes, List[bytes]] = request.args  # type: ignore
-
-            remote_room_hosts = parse_strings_from_args(
-                args, "server_name", required=False
-            )
-        elif RoomAlias.is_valid(room_identifier):
-            handler = self.room_member_handler
-            room_alias = RoomAlias.from_string(room_identifier)
-            room_id_obj, remote_room_hosts = await handler.lookup_room_alias(room_alias)
-            room_id = room_id_obj.to_string()
-        else:
-            raise SynapseError(
-                400, "%s was not legal room ID or room alias" % (room_identifier,)
-            )
-
-        await self.room_member_handler.update_membership(
-            requester=requester,
-            target=requester.user,
-            room_id=room_id,
-            action=Membership.KNOCK,
-            txn_id=txn_id,
-            third_party_signed=None,
-            remote_room_hosts=remote_room_hosts,
-            content=event_content,
-        )
-
-        return 200, {"room_id": room_id}
-
-    def on_PUT(self, request: Request, room_identifier: str, txn_id: str):
-        set_tag("txn_id", txn_id)
-
-        return self.txns.fetch_or_execute_request(
-            request, self.on_POST, request, room_identifier, txn_id
-        )
-
-
-def register_servlets(hs, http_server):
-    KnockRoomAliasServlet(hs).register(http_server)
diff --git a/synapse/rest/client/v2_alpha/notifications.py b/synapse/rest/client/v2_alpha/notifications.py
deleted file mode 100644
index 0ede643c2d..0000000000
--- a/synapse/rest/client/v2_alpha/notifications.py
+++ /dev/null
@@ -1,91 +0,0 @@
-# Copyright 2016 OpenMarket Ltd
-#
-# 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.
-
-import logging
-
-from synapse.events.utils import format_event_for_client_v2_without_room_id
-from synapse.http.servlet import RestServlet, parse_integer, parse_string
-
-from ._base import client_patterns
-
-logger = logging.getLogger(__name__)
-
-
-class NotificationsServlet(RestServlet):
-    PATTERNS = client_patterns("/notifications$")
-
-    def __init__(self, hs):
-        super().__init__()
-        self.store = hs.get_datastore()
-        self.auth = hs.get_auth()
-        self.clock = hs.get_clock()
-        self._event_serializer = hs.get_event_client_serializer()
-
-    async def on_GET(self, request):
-        requester = await self.auth.get_user_by_req(request)
-        user_id = requester.user.to_string()
-
-        from_token = parse_string(request, "from", required=False)
-        limit = parse_integer(request, "limit", default=50)
-        only = parse_string(request, "only", required=False)
-
-        limit = min(limit, 500)
-
-        push_actions = await self.store.get_push_actions_for_user(
-            user_id, from_token, limit, only_highlight=(only == "highlight")
-        )
-
-        receipts_by_room = await self.store.get_receipts_for_user_with_orderings(
-            user_id, "m.read"
-        )
-
-        notif_event_ids = [pa["event_id"] for pa in push_actions]
-        notif_events = await self.store.get_events(notif_event_ids)
-
-        returned_push_actions = []
-
-        next_token = None
-
-        for pa in push_actions:
-            returned_pa = {
-                "room_id": pa["room_id"],
-                "profile_tag": pa["profile_tag"],
-                "actions": pa["actions"],
-                "ts": pa["received_ts"],
-                "event": (
-                    await self._event_serializer.serialize_event(
-                        notif_events[pa["event_id"]],
-                        self.clock.time_msec(),
-                        event_format=format_event_for_client_v2_without_room_id,
-                    )
-                ),
-            }
-
-            if pa["room_id"] not in receipts_by_room:
-                returned_pa["read"] = False
-            else:
-                receipt = receipts_by_room[pa["room_id"]]
-
-                returned_pa["read"] = (
-                    receipt["topological_ordering"],
-                    receipt["stream_ordering"],
-                ) >= (pa["topological_ordering"], pa["stream_ordering"])
-            returned_push_actions.append(returned_pa)
-            next_token = str(pa["stream_ordering"])
-
-        return 200, {"notifications": returned_push_actions, "next_token": next_token}
-
-
-def register_servlets(hs, http_server):
-    NotificationsServlet(hs).register(http_server)
diff --git a/synapse/rest/client/v2_alpha/openid.py b/synapse/rest/client/v2_alpha/openid.py
deleted file mode 100644
index e8d2673819..0000000000
--- a/synapse/rest/client/v2_alpha/openid.py
+++ /dev/null
@@ -1,94 +0,0 @@
-# Copyright 2015, 2016 OpenMarket Ltd
-#
-# 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.
-
-
-import logging
-
-from synapse.api.errors import AuthError
-from synapse.http.servlet import RestServlet, parse_json_object_from_request
-from synapse.util.stringutils import random_string
-
-from ._base import client_patterns
-
-logger = logging.getLogger(__name__)
-
-
-class IdTokenServlet(RestServlet):
-    """
-    Get a bearer token that may be passed to a third party to confirm ownership
-    of a matrix user id.
-
-    The format of the response could be made compatible with the format given
-    in http://openid.net/specs/openid-connect-core-1_0.html#TokenResponse
-
-    But instead of returning a signed "id_token" the response contains the
-    name of the issuing matrix homeserver. This means that for now the third
-    party will need to check the validity of the "id_token" against the
-    federation /openid/userinfo endpoint of the homeserver.
-
-    Request:
-
-    POST /user/{user_id}/openid/request_token?access_token=... HTTP/1.1
-
-    {}
-
-    Response:
-
-    HTTP/1.1 200 OK
-    {
-        "access_token": "ABDEFGH",
-        "token_type": "Bearer",
-        "matrix_server_name": "example.com",
-        "expires_in": 3600,
-    }
-    """
-
-    PATTERNS = client_patterns("/user/(?P<user_id>[^/]*)/openid/request_token")
-
-    EXPIRES_MS = 3600 * 1000
-
-    def __init__(self, hs):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.store = hs.get_datastore()
-        self.clock = hs.get_clock()
-        self.server_name = hs.config.server_name
-
-    async def on_POST(self, request, user_id):
-        requester = await self.auth.get_user_by_req(request)
-        if user_id != requester.user.to_string():
-            raise AuthError(403, "Cannot request tokens for other users.")
-
-        # Parse the request body to make sure it's JSON, but ignore the contents
-        # for now.
-        parse_json_object_from_request(request)
-
-        token = random_string(24)
-        ts_valid_until_ms = self.clock.time_msec() + self.EXPIRES_MS
-
-        await self.store.insert_open_id_token(token, ts_valid_until_ms, user_id)
-
-        return (
-            200,
-            {
-                "access_token": token,
-                "token_type": "Bearer",
-                "matrix_server_name": self.server_name,
-                "expires_in": self.EXPIRES_MS // 1000,
-            },
-        )
-
-
-def register_servlets(hs, http_server):
-    IdTokenServlet(hs).register(http_server)
diff --git a/synapse/rest/client/v2_alpha/password_policy.py b/synapse/rest/client/v2_alpha/password_policy.py
deleted file mode 100644
index a83927aee6..0000000000
--- a/synapse/rest/client/v2_alpha/password_policy.py
+++ /dev/null
@@ -1,57 +0,0 @@
-# Copyright 2019 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.
-
-import logging
-
-from synapse.http.servlet import RestServlet
-
-from ._base import client_patterns
-
-logger = logging.getLogger(__name__)
-
-
-class PasswordPolicyServlet(RestServlet):
-    PATTERNS = client_patterns("/password_policy$")
-
-    def __init__(self, hs):
-        """
-        Args:
-            hs (synapse.server.HomeServer): server
-        """
-        super().__init__()
-
-        self.policy = hs.config.password_policy
-        self.enabled = hs.config.password_policy_enabled
-
-    def on_GET(self, request):
-        if not self.enabled or not self.policy:
-            return (200, {})
-
-        policy = {}
-
-        for param in [
-            "minimum_length",
-            "require_digit",
-            "require_symbol",
-            "require_lowercase",
-            "require_uppercase",
-        ]:
-            if param in self.policy:
-                policy["m.%s" % param] = self.policy[param]
-
-        return (200, policy)
-
-
-def register_servlets(hs, http_server):
-    PasswordPolicyServlet(hs).register(http_server)
diff --git a/synapse/rest/client/v2_alpha/read_marker.py b/synapse/rest/client/v2_alpha/read_marker.py
deleted file mode 100644
index 027f8b81fa..0000000000
--- a/synapse/rest/client/v2_alpha/read_marker.py
+++ /dev/null
@@ -1,74 +0,0 @@
-# Copyright 2017 Vector Creations Ltd
-#
-# 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.
-
-import logging
-
-from synapse.api.constants import ReadReceiptEventFields
-from synapse.api.errors import Codes, SynapseError
-from synapse.http.servlet import RestServlet, parse_json_object_from_request
-
-from ._base import client_patterns
-
-logger = logging.getLogger(__name__)
-
-
-class ReadMarkerRestServlet(RestServlet):
-    PATTERNS = client_patterns("/rooms/(?P<room_id>[^/]*)/read_markers$")
-
-    def __init__(self, hs):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.receipts_handler = hs.get_receipts_handler()
-        self.read_marker_handler = hs.get_read_marker_handler()
-        self.presence_handler = hs.get_presence_handler()
-
-    async def on_POST(self, request, room_id):
-        requester = await self.auth.get_user_by_req(request)
-
-        await self.presence_handler.bump_presence_active_time(requester.user)
-
-        body = parse_json_object_from_request(request)
-        read_event_id = body.get("m.read", None)
-        hidden = body.get(ReadReceiptEventFields.MSC2285_HIDDEN, False)
-
-        if not isinstance(hidden, bool):
-            raise SynapseError(
-                400,
-                "Param %s must be a boolean, if given"
-                % ReadReceiptEventFields.MSC2285_HIDDEN,
-                Codes.BAD_JSON,
-            )
-
-        if read_event_id:
-            await self.receipts_handler.received_client_receipt(
-                room_id,
-                "m.read",
-                user_id=requester.user.to_string(),
-                event_id=read_event_id,
-                hidden=hidden,
-            )
-
-        read_marker_event_id = body.get("m.fully_read", None)
-        if read_marker_event_id:
-            await self.read_marker_handler.received_client_read_marker(
-                room_id,
-                user_id=requester.user.to_string(),
-                event_id=read_marker_event_id,
-            )
-
-        return 200, {}
-
-
-def register_servlets(hs, http_server):
-    ReadMarkerRestServlet(hs).register(http_server)
diff --git a/synapse/rest/client/v2_alpha/receipts.py b/synapse/rest/client/v2_alpha/receipts.py
deleted file mode 100644
index d9ab836cd8..0000000000
--- a/synapse/rest/client/v2_alpha/receipts.py
+++ /dev/null
@@ -1,71 +0,0 @@
-# Copyright 2015, 2016 OpenMarket Ltd
-#
-# 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.
-
-import logging
-
-from synapse.api.constants import ReadReceiptEventFields
-from synapse.api.errors import Codes, SynapseError
-from synapse.http.servlet import RestServlet, parse_json_object_from_request
-
-from ._base import client_patterns
-
-logger = logging.getLogger(__name__)
-
-
-class ReceiptRestServlet(RestServlet):
-    PATTERNS = client_patterns(
-        "/rooms/(?P<room_id>[^/]*)"
-        "/receipt/(?P<receipt_type>[^/]*)"
-        "/(?P<event_id>[^/]*)$"
-    )
-
-    def __init__(self, hs):
-        super().__init__()
-        self.hs = hs
-        self.auth = hs.get_auth()
-        self.receipts_handler = hs.get_receipts_handler()
-        self.presence_handler = hs.get_presence_handler()
-
-    async def on_POST(self, request, room_id, receipt_type, event_id):
-        requester = await self.auth.get_user_by_req(request)
-
-        if receipt_type != "m.read":
-            raise SynapseError(400, "Receipt type must be 'm.read'")
-
-        body = parse_json_object_from_request(request, allow_empty_body=True)
-        hidden = body.get(ReadReceiptEventFields.MSC2285_HIDDEN, False)
-
-        if not isinstance(hidden, bool):
-            raise SynapseError(
-                400,
-                "Param %s must be a boolean, if given"
-                % ReadReceiptEventFields.MSC2285_HIDDEN,
-                Codes.BAD_JSON,
-            )
-
-        await self.presence_handler.bump_presence_active_time(requester.user)
-
-        await self.receipts_handler.received_client_receipt(
-            room_id,
-            receipt_type,
-            user_id=requester.user.to_string(),
-            event_id=event_id,
-            hidden=hidden,
-        )
-
-        return 200, {}
-
-
-def register_servlets(hs, http_server):
-    ReceiptRestServlet(hs).register(http_server)
diff --git a/synapse/rest/client/v2_alpha/register.py b/synapse/rest/client/v2_alpha/register.py
deleted file mode 100644
index 4d31584acd..0000000000
--- a/synapse/rest/client/v2_alpha/register.py
+++ /dev/null
@@ -1,879 +0,0 @@
-# Copyright 2015 - 2016 OpenMarket Ltd
-# Copyright 2017 Vector Creations Ltd
-#
-# 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.
-import hmac
-import logging
-import random
-from typing import List, Union
-
-import synapse
-import synapse.api.auth
-import synapse.types
-from synapse.api.constants import APP_SERVICE_REGISTRATION_TYPE, LoginType
-from synapse.api.errors import (
-    Codes,
-    InteractiveAuthIncompleteError,
-    SynapseError,
-    ThreepidValidationError,
-    UnrecognizedRequestError,
-)
-from synapse.config import ConfigError
-from synapse.config.captcha import CaptchaConfig
-from synapse.config.consent import ConsentConfig
-from synapse.config.emailconfig import ThreepidBehaviour
-from synapse.config.ratelimiting import FederationRateLimitConfig
-from synapse.config.registration import RegistrationConfig
-from synapse.config.server import is_threepid_reserved
-from synapse.handlers.auth import AuthHandler
-from synapse.handlers.ui_auth import UIAuthSessionDataConstants
-from synapse.http.server import finish_request, respond_with_html
-from synapse.http.servlet import (
-    RestServlet,
-    assert_params_in_dict,
-    parse_boolean,
-    parse_json_object_from_request,
-    parse_string,
-)
-from synapse.metrics import threepid_send_requests
-from synapse.push.mailer import Mailer
-from synapse.types import JsonDict
-from synapse.util.msisdn import phone_number_to_msisdn
-from synapse.util.ratelimitutils import FederationRateLimiter
-from synapse.util.stringutils import assert_valid_client_secret, random_string
-from synapse.util.threepids import (
-    canonicalise_email,
-    check_3pid_allowed,
-    validate_email,
-)
-
-from ._base import client_patterns, interactive_auth_handler
-
-# We ought to be using hmac.compare_digest() but on older pythons it doesn't
-# exist. It's a _really minor_ security flaw to use plain string comparison
-# because the timing attack is so obscured by all the other code here it's
-# unlikely to make much difference
-if hasattr(hmac, "compare_digest"):
-    compare_digest = hmac.compare_digest
-else:
-
-    def compare_digest(a, b):
-        return a == b
-
-
-logger = logging.getLogger(__name__)
-
-
-class EmailRegisterRequestTokenRestServlet(RestServlet):
-    PATTERNS = client_patterns("/register/email/requestToken$")
-
-    def __init__(self, hs):
-        """
-        Args:
-            hs (synapse.server.HomeServer): server
-        """
-        super().__init__()
-        self.hs = hs
-        self.identity_handler = hs.get_identity_handler()
-        self.config = hs.config
-
-        if self.hs.config.threepid_behaviour_email == ThreepidBehaviour.LOCAL:
-            self.mailer = Mailer(
-                hs=self.hs,
-                app_name=self.config.email_app_name,
-                template_html=self.config.email_registration_template_html,
-                template_text=self.config.email_registration_template_text,
-            )
-
-    async def on_POST(self, request):
-        if self.hs.config.threepid_behaviour_email == ThreepidBehaviour.OFF:
-            if self.hs.config.local_threepid_handling_disabled_due_to_email_config:
-                logger.warning(
-                    "Email registration has been disabled due to lack of email config"
-                )
-            raise SynapseError(
-                400, "Email-based registration has been disabled on this server"
-            )
-        body = parse_json_object_from_request(request)
-
-        assert_params_in_dict(body, ["client_secret", "email", "send_attempt"])
-
-        # Extract params from body
-        client_secret = body["client_secret"]
-        assert_valid_client_secret(client_secret)
-
-        # For emails, canonicalise the address.
-        # We store all email addresses canonicalised in the DB.
-        # (See on_POST in EmailThreepidRequestTokenRestServlet
-        # in synapse/rest/client/v2_alpha/account.py)
-        try:
-            email = validate_email(body["email"])
-        except ValueError as e:
-            raise SynapseError(400, str(e))
-        send_attempt = body["send_attempt"]
-        next_link = body.get("next_link")  # Optional param
-
-        if not check_3pid_allowed(self.hs, "email", email):
-            raise SynapseError(
-                403,
-                "Your email domain is not authorized to register on this server",
-                Codes.THREEPID_DENIED,
-            )
-
-        await self.identity_handler.ratelimit_request_token_requests(
-            request, "email", email
-        )
-
-        existing_user_id = await self.hs.get_datastore().get_user_id_by_threepid(
-            "email", email
-        )
-
-        if existing_user_id is not None:
-            if self.hs.config.request_token_inhibit_3pid_errors:
-                # Make the client think the operation succeeded. See the rationale in the
-                # comments for request_token_inhibit_3pid_errors.
-                # Also wait for some random amount of time between 100ms and 1s to make it
-                # look like we did something.
-                await self.hs.get_clock().sleep(random.randint(1, 10) / 10)
-                return 200, {"sid": random_string(16)}
-
-            raise SynapseError(400, "Email is already in use", Codes.THREEPID_IN_USE)
-
-        if self.config.threepid_behaviour_email == ThreepidBehaviour.REMOTE:
-            assert self.hs.config.account_threepid_delegate_email
-
-            # Have the configured identity server handle the request
-            ret = await self.identity_handler.requestEmailToken(
-                self.hs.config.account_threepid_delegate_email,
-                email,
-                client_secret,
-                send_attempt,
-                next_link,
-            )
-        else:
-            # Send registration emails from Synapse
-            sid = await self.identity_handler.send_threepid_validation(
-                email,
-                client_secret,
-                send_attempt,
-                self.mailer.send_registration_mail,
-                next_link,
-            )
-
-            # Wrap the session id in a JSON object
-            ret = {"sid": sid}
-
-        threepid_send_requests.labels(type="email", reason="register").observe(
-            send_attempt
-        )
-
-        return 200, ret
-
-
-class MsisdnRegisterRequestTokenRestServlet(RestServlet):
-    PATTERNS = client_patterns("/register/msisdn/requestToken$")
-
-    def __init__(self, hs):
-        """
-        Args:
-            hs (synapse.server.HomeServer): server
-        """
-        super().__init__()
-        self.hs = hs
-        self.identity_handler = hs.get_identity_handler()
-
-    async def on_POST(self, request):
-        body = parse_json_object_from_request(request)
-
-        assert_params_in_dict(
-            body, ["client_secret", "country", "phone_number", "send_attempt"]
-        )
-        client_secret = body["client_secret"]
-        assert_valid_client_secret(client_secret)
-        country = body["country"]
-        phone_number = body["phone_number"]
-        send_attempt = body["send_attempt"]
-        next_link = body.get("next_link")  # Optional param
-
-        msisdn = phone_number_to_msisdn(country, phone_number)
-
-        if not check_3pid_allowed(self.hs, "msisdn", msisdn):
-            raise SynapseError(
-                403,
-                "Phone numbers are not authorized to register on this server",
-                Codes.THREEPID_DENIED,
-            )
-
-        await self.identity_handler.ratelimit_request_token_requests(
-            request, "msisdn", msisdn
-        )
-
-        existing_user_id = await self.hs.get_datastore().get_user_id_by_threepid(
-            "msisdn", msisdn
-        )
-
-        if existing_user_id is not None:
-            if self.hs.config.request_token_inhibit_3pid_errors:
-                # Make the client think the operation succeeded. See the rationale in the
-                # comments for request_token_inhibit_3pid_errors.
-                # Also wait for some random amount of time between 100ms and 1s to make it
-                # look like we did something.
-                await self.hs.get_clock().sleep(random.randint(1, 10) / 10)
-                return 200, {"sid": random_string(16)}
-
-            raise SynapseError(
-                400, "Phone number is already in use", Codes.THREEPID_IN_USE
-            )
-
-        if not self.hs.config.account_threepid_delegate_msisdn:
-            logger.warning(
-                "No upstream msisdn account_threepid_delegate configured on the server to "
-                "handle this request"
-            )
-            raise SynapseError(
-                400, "Registration by phone number is not supported on this homeserver"
-            )
-
-        ret = await self.identity_handler.requestMsisdnToken(
-            self.hs.config.account_threepid_delegate_msisdn,
-            country,
-            phone_number,
-            client_secret,
-            send_attempt,
-            next_link,
-        )
-
-        threepid_send_requests.labels(type="msisdn", reason="register").observe(
-            send_attempt
-        )
-
-        return 200, ret
-
-
-class RegistrationSubmitTokenServlet(RestServlet):
-    """Handles registration 3PID validation token submission"""
-
-    PATTERNS = client_patterns(
-        "/registration/(?P<medium>[^/]*)/submit_token$", releases=(), unstable=True
-    )
-
-    def __init__(self, hs):
-        """
-        Args:
-            hs (synapse.server.HomeServer): server
-        """
-        super().__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_registration_template_failure_html
-            )
-
-    async def on_GET(self, request, medium):
-        if medium != "email":
-            raise SynapseError(
-                400, "This medium is currently not supported for registration"
-            )
-        if self.config.threepid_behaviour_email == ThreepidBehaviour.OFF:
-            if self.config.local_threepid_handling_disabled_due_to_email_config:
-                logger.warning(
-                    "User registration via email has been disabled due to lack of email config"
-                )
-            raise SynapseError(
-                400, "Email-based registration is disabled on this server"
-            )
-
-        sid = parse_string(request, "sid", required=True)
-        client_secret = parse_string(request, "client_secret", required=True)
-        assert_valid_client_secret(client_secret)
-        token = parse_string(request, "token", required=True)
-
-        # 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_registration_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 UsernameAvailabilityRestServlet(RestServlet):
-    PATTERNS = client_patterns("/register/available")
-
-    def __init__(self, hs):
-        """
-        Args:
-            hs (synapse.server.HomeServer): server
-        """
-        super().__init__()
-        self.hs = hs
-        self.registration_handler = hs.get_registration_handler()
-        self.ratelimiter = FederationRateLimiter(
-            hs.get_clock(),
-            FederationRateLimitConfig(
-                # Time window of 2s
-                window_size=2000,
-                # Artificially delay requests if rate > sleep_limit/window_size
-                sleep_limit=1,
-                # Amount of artificial delay to apply
-                sleep_msec=1000,
-                # Error with 429 if more than reject_limit requests are queued
-                reject_limit=1,
-                # Allow 1 request at a time
-                concurrent_requests=1,
-            ),
-        )
-
-    async def on_GET(self, request):
-        if not self.hs.config.enable_registration:
-            raise SynapseError(
-                403, "Registration has been disabled", errcode=Codes.FORBIDDEN
-            )
-
-        ip = request.getClientIP()
-        with self.ratelimiter.ratelimit(ip) as wait_deferred:
-            await wait_deferred
-
-            username = parse_string(request, "username", required=True)
-
-            await self.registration_handler.check_username(username)
-
-            return 200, {"available": True}
-
-
-class RegisterRestServlet(RestServlet):
-    PATTERNS = client_patterns("/register$")
-
-    def __init__(self, hs):
-        """
-        Args:
-            hs (synapse.server.HomeServer): server
-        """
-        super().__init__()
-
-        self.hs = hs
-        self.auth = hs.get_auth()
-        self.store = hs.get_datastore()
-        self.auth_handler = hs.get_auth_handler()
-        self.registration_handler = hs.get_registration_handler()
-        self.identity_handler = hs.get_identity_handler()
-        self.room_member_handler = hs.get_room_member_handler()
-        self.macaroon_gen = hs.get_macaroon_generator()
-        self.ratelimiter = hs.get_registration_ratelimiter()
-        self.password_policy_handler = hs.get_password_policy_handler()
-        self.clock = hs.get_clock()
-        self._registration_enabled = self.hs.config.enable_registration
-        self._msc2918_enabled = hs.config.access_token_lifetime is not None
-
-        self._registration_flows = _calculate_registration_flows(
-            hs.config, self.auth_handler
-        )
-
-    @interactive_auth_handler
-    async def on_POST(self, request):
-        body = parse_json_object_from_request(request)
-
-        client_addr = request.getClientIP()
-
-        await self.ratelimiter.ratelimit(None, client_addr, update=False)
-
-        kind = b"user"
-        if b"kind" in request.args:
-            kind = request.args[b"kind"][0]
-
-        if kind == b"guest":
-            ret = await self._do_guest_registration(body, address=client_addr)
-            return ret
-        elif kind != b"user":
-            raise UnrecognizedRequestError(
-                "Do not understand membership kind: %s" % (kind.decode("utf8"),)
-            )
-
-        if self._msc2918_enabled:
-            # Check if this registration should also issue a refresh token, as
-            # per MSC2918
-            should_issue_refresh_token = parse_boolean(
-                request, name="org.matrix.msc2918.refresh_token", default=False
-            )
-        else:
-            should_issue_refresh_token = False
-
-        # Pull out the provided username and do basic sanity checks early since
-        # the auth layer will store these in sessions.
-        desired_username = None
-        if "username" in body:
-            if not isinstance(body["username"], str) or len(body["username"]) > 512:
-                raise SynapseError(400, "Invalid username")
-            desired_username = body["username"]
-
-        # fork off as soon as possible for ASes which have completely
-        # different registration flows to normal users
-
-        # == Application Service Registration ==
-        if body.get("type") == APP_SERVICE_REGISTRATION_TYPE:
-            if not self.auth.has_access_token(request):
-                raise SynapseError(
-                    400,
-                    "Appservice token must be provided when using a type of m.login.application_service",
-                )
-
-            # Verify the AS
-            self.auth.get_appservice_by_req(request)
-
-            # Set the desired user according to the AS API (which uses the
-            # 'user' key not 'username'). Since this is a new addition, we'll
-            # fallback to 'username' if they gave one.
-            desired_username = body.get("user", desired_username)
-
-            # XXX we should check that desired_username is valid. Currently
-            # we give appservices carte blanche for any insanity in mxids,
-            # because the IRC bridges rely on being able to register stupid
-            # IDs.
-
-            access_token = self.auth.get_access_token_from_request(request)
-
-            if not isinstance(desired_username, str):
-                raise SynapseError(400, "Desired Username is missing or not a string")
-
-            result = await self._do_appservice_registration(
-                desired_username,
-                access_token,
-                body,
-                should_issue_refresh_token=should_issue_refresh_token,
-            )
-
-            return 200, result
-        elif self.auth.has_access_token(request):
-            raise SynapseError(
-                400,
-                "An access token should not be provided on requests to /register (except if type is m.login.application_service)",
-            )
-
-        # == Normal User Registration == (everyone else)
-        if not self._registration_enabled:
-            raise SynapseError(403, "Registration has been disabled", Codes.FORBIDDEN)
-
-        # For regular registration, convert the provided username to lowercase
-        # before attempting to register it. This should mean that people who try
-        # to register with upper-case in their usernames don't get a nasty surprise.
-        #
-        # Note that we treat usernames case-insensitively in login, so they are
-        # free to carry on imagining that their username is CrAzYh4cKeR if that
-        # keeps them happy.
-        if desired_username is not None:
-            desired_username = desired_username.lower()
-
-        # Check if this account is upgrading from a guest account.
-        guest_access_token = body.get("guest_access_token", None)
-
-        # Pull out the provided password and do basic sanity checks early.
-        #
-        # Note that we remove the password from the body since the auth layer
-        # will store the body in the session and we don't want a plaintext
-        # password store there.
-        password = body.pop("password", None)
-        if password is not None:
-            if not isinstance(password, str) or len(password) > 512:
-                raise SynapseError(400, "Invalid password")
-            self.password_policy_handler.validate_password(password)
-
-        if "initial_device_display_name" in body and password is None:
-            # ignore 'initial_device_display_name' if sent without
-            # a password to work around a client bug where it sent
-            # the 'initial_device_display_name' param alone, wiping out
-            # the original registration params
-            logger.warning("Ignoring initial_device_display_name without password")
-            del body["initial_device_display_name"]
-
-        session_id = self.auth_handler.get_session_id(body)
-        registered_user_id = None
-        password_hash = None
-        if session_id:
-            # if we get a registered user id out of here, it means we previously
-            # registered a user for this session, so we could just return the
-            # user here. We carry on and go through the auth checks though,
-            # for paranoia.
-            registered_user_id = await self.auth_handler.get_session_data(
-                session_id, UIAuthSessionDataConstants.REGISTERED_USER_ID, None
-            )
-            # Extract the previously-hashed password from the session.
-            password_hash = await self.auth_handler.get_session_data(
-                session_id, UIAuthSessionDataConstants.PASSWORD_HASH, None
-            )
-
-        # Ensure that the username is valid.
-        if desired_username is not None:
-            await self.registration_handler.check_username(
-                desired_username,
-                guest_access_token=guest_access_token,
-                assigned_user_id=registered_user_id,
-            )
-
-        # Check if the user-interactive authentication flows are complete, if
-        # not this will raise a user-interactive auth error.
-        try:
-            auth_result, params, session_id = await self.auth_handler.check_ui_auth(
-                self._registration_flows,
-                request,
-                body,
-                "register a new account",
-            )
-        except InteractiveAuthIncompleteError as e:
-            # The user needs to provide more steps to complete auth.
-            #
-            # Hash the password and store it with the session since the client
-            # is not required to provide the password again.
-            #
-            # If a password hash was previously stored we will not attempt to
-            # re-hash and store it for efficiency. This assumes the password
-            # does not change throughout the authentication flow, but this
-            # should be fine since the data is meant to be consistent.
-            if not password_hash and password:
-                password_hash = await self.auth_handler.hash(password)
-                await self.auth_handler.set_session_data(
-                    e.session_id,
-                    UIAuthSessionDataConstants.PASSWORD_HASH,
-                    password_hash,
-                )
-            raise
-
-        # Check that we're not trying to register a denied 3pid.
-        #
-        # the user-facing checks will probably already have happened in
-        # /register/email/requestToken when we requested a 3pid, but that's not
-        # guaranteed.
-        if auth_result:
-            for login_type in [LoginType.EMAIL_IDENTITY, LoginType.MSISDN]:
-                if login_type in auth_result:
-                    medium = auth_result[login_type]["medium"]
-                    address = auth_result[login_type]["address"]
-
-                    if not check_3pid_allowed(self.hs, medium, address):
-                        raise SynapseError(
-                            403,
-                            "Third party identifiers (email/phone numbers)"
-                            + " are not authorized on this server",
-                            Codes.THREEPID_DENIED,
-                        )
-
-        if registered_user_id is not None:
-            logger.info(
-                "Already registered user ID %r for this session", registered_user_id
-            )
-            # don't re-register the threepids
-            registered = False
-        else:
-            # If we have a password in this request, prefer it. Otherwise, there
-            # might be a password hash from an earlier request.
-            if password:
-                password_hash = await self.auth_handler.hash(password)
-            if not password_hash:
-                raise SynapseError(400, "Missing params: password", Codes.MISSING_PARAM)
-
-            desired_username = params.get("username", None)
-            guest_access_token = params.get("guest_access_token", None)
-
-            if desired_username is not None:
-                desired_username = desired_username.lower()
-
-            threepid = None
-            if auth_result:
-                threepid = auth_result.get(LoginType.EMAIL_IDENTITY)
-
-                # Also check that we're not trying to register a 3pid that's already
-                # been registered.
-                #
-                # This has probably happened in /register/email/requestToken as well,
-                # but if a user hits this endpoint twice then clicks on each link from
-                # the two activation emails, they would register the same 3pid twice.
-                for login_type in [LoginType.EMAIL_IDENTITY, LoginType.MSISDN]:
-                    if login_type in auth_result:
-                        medium = auth_result[login_type]["medium"]
-                        address = auth_result[login_type]["address"]
-                        # For emails, canonicalise the address.
-                        # We store all email addresses canonicalised in the DB.
-                        # (See on_POST in EmailThreepidRequestTokenRestServlet
-                        # in synapse/rest/client/v2_alpha/account.py)
-                        if medium == "email":
-                            try:
-                                address = canonicalise_email(address)
-                            except ValueError as e:
-                                raise SynapseError(400, str(e))
-
-                        existing_user_id = await self.store.get_user_id_by_threepid(
-                            medium, address
-                        )
-
-                        if existing_user_id is not None:
-                            raise SynapseError(
-                                400,
-                                "%s is already in use" % medium,
-                                Codes.THREEPID_IN_USE,
-                            )
-
-            entries = await self.store.get_user_agents_ips_to_ui_auth_session(
-                session_id
-            )
-
-            registered_user_id = await self.registration_handler.register_user(
-                localpart=desired_username,
-                password_hash=password_hash,
-                guest_access_token=guest_access_token,
-                threepid=threepid,
-                address=client_addr,
-                user_agent_ips=entries,
-            )
-            # Necessary due to auth checks prior to the threepid being
-            # written to the db
-            if threepid:
-                if is_threepid_reserved(
-                    self.hs.config.mau_limits_reserved_threepids, threepid
-                ):
-                    await self.store.upsert_monthly_active_user(registered_user_id)
-
-            # Remember that the user account has been registered (and the user
-            # ID it was registered with, since it might not have been specified).
-            await self.auth_handler.set_session_data(
-                session_id,
-                UIAuthSessionDataConstants.REGISTERED_USER_ID,
-                registered_user_id,
-            )
-
-            registered = True
-
-        return_dict = await self._create_registration_details(
-            registered_user_id,
-            params,
-            should_issue_refresh_token=should_issue_refresh_token,
-        )
-
-        if registered:
-            await self.registration_handler.post_registration_actions(
-                user_id=registered_user_id,
-                auth_result=auth_result,
-                access_token=return_dict.get("access_token"),
-            )
-
-        return 200, return_dict
-
-    async def _do_appservice_registration(
-        self, username, as_token, body, should_issue_refresh_token: bool = False
-    ):
-        user_id = await self.registration_handler.appservice_register(
-            username, as_token
-        )
-        return await self._create_registration_details(
-            user_id,
-            body,
-            is_appservice_ghost=True,
-            should_issue_refresh_token=should_issue_refresh_token,
-        )
-
-    async def _create_registration_details(
-        self,
-        user_id: str,
-        params: JsonDict,
-        is_appservice_ghost: bool = False,
-        should_issue_refresh_token: bool = False,
-    ):
-        """Complete registration of newly-registered user
-
-        Allocates device_id if one was not given; also creates access_token.
-
-        Args:
-            user_id: full canonical @user:id
-            params: registration parameters, from which we pull device_id,
-                initial_device_name and inhibit_login
-            is_appservice_ghost
-            should_issue_refresh_token: True if this registration should issue
-                a refresh token alongside the access token.
-        Returns:
-             dictionary for response from /register
-        """
-        result = {"user_id": user_id, "home_server": self.hs.hostname}
-        if not params.get("inhibit_login", False):
-            device_id = params.get("device_id")
-            initial_display_name = params.get("initial_device_display_name")
-            (
-                device_id,
-                access_token,
-                valid_until_ms,
-                refresh_token,
-            ) = await self.registration_handler.register_device(
-                user_id,
-                device_id,
-                initial_display_name,
-                is_guest=False,
-                is_appservice_ghost=is_appservice_ghost,
-                should_issue_refresh_token=should_issue_refresh_token,
-            )
-
-            result.update({"access_token": access_token, "device_id": device_id})
-
-            if valid_until_ms is not None:
-                expires_in_ms = valid_until_ms - self.clock.time_msec()
-                result["expires_in_ms"] = expires_in_ms
-
-            if refresh_token is not None:
-                result["refresh_token"] = refresh_token
-
-        return result
-
-    async def _do_guest_registration(self, params, address=None):
-        if not self.hs.config.allow_guest_access:
-            raise SynapseError(403, "Guest access is disabled")
-        user_id = await self.registration_handler.register_user(
-            make_guest=True, address=address
-        )
-
-        # we don't allow guests to specify their own device_id, because
-        # we have nowhere to store it.
-        device_id = synapse.api.auth.GUEST_DEVICE_ID
-        initial_display_name = params.get("initial_device_display_name")
-        (
-            device_id,
-            access_token,
-            valid_until_ms,
-            refresh_token,
-        ) = await self.registration_handler.register_device(
-            user_id, device_id, initial_display_name, is_guest=True
-        )
-
-        result = {
-            "user_id": user_id,
-            "device_id": device_id,
-            "access_token": access_token,
-            "home_server": self.hs.hostname,
-        }
-
-        if valid_until_ms is not None:
-            expires_in_ms = valid_until_ms - self.clock.time_msec()
-            result["expires_in_ms"] = expires_in_ms
-
-        if refresh_token is not None:
-            result["refresh_token"] = refresh_token
-
-        return 200, result
-
-
-def _calculate_registration_flows(
-    # technically `config` has to provide *all* of these interfaces, not just one
-    config: Union[RegistrationConfig, ConsentConfig, CaptchaConfig],
-    auth_handler: AuthHandler,
-) -> List[List[str]]:
-    """Get a suitable flows list for registration
-
-    Args:
-        config: server configuration
-        auth_handler: authorization handler
-
-    Returns: a list of supported flows
-    """
-    # FIXME: need a better error than "no auth flow found" for scenarios
-    # where we required 3PID for registration but the user didn't give one
-    require_email = "email" in config.registrations_require_3pid
-    require_msisdn = "msisdn" in config.registrations_require_3pid
-
-    show_msisdn = True
-    show_email = True
-
-    if config.disable_msisdn_registration:
-        show_msisdn = False
-        require_msisdn = False
-
-    enabled_auth_types = auth_handler.get_enabled_auth_types()
-    if LoginType.EMAIL_IDENTITY not in enabled_auth_types:
-        show_email = False
-        if require_email:
-            raise ConfigError(
-                "Configuration requires email address at registration, but email "
-                "validation is not configured"
-            )
-
-    if LoginType.MSISDN not in enabled_auth_types:
-        show_msisdn = False
-        if require_msisdn:
-            raise ConfigError(
-                "Configuration requires msisdn at registration, but msisdn "
-                "validation is not configured"
-            )
-
-    flows = []
-
-    # only support 3PIDless registration if no 3PIDs are required
-    if not require_email and not require_msisdn:
-        # Add a dummy step here, otherwise if a client completes
-        # recaptcha first we'll assume they were going for this flow
-        # and complete the request, when they could have been trying to
-        # complete one of the flows with email/msisdn auth.
-        flows.append([LoginType.DUMMY])
-
-    # only support the email-only flow if we don't require MSISDN 3PIDs
-    if show_email and not require_msisdn:
-        flows.append([LoginType.EMAIL_IDENTITY])
-
-    # only support the MSISDN-only flow if we don't require email 3PIDs
-    if show_msisdn and not require_email:
-        flows.append([LoginType.MSISDN])
-
-    if show_email and show_msisdn:
-        # always let users provide both MSISDN & email
-        flows.append([LoginType.MSISDN, LoginType.EMAIL_IDENTITY])
-
-    # Prepend m.login.terms to all flows if we're requiring consent
-    if config.user_consent_at_registration:
-        for flow in flows:
-            flow.insert(0, LoginType.TERMS)
-
-    # Prepend recaptcha to all flows if we're requiring captcha
-    if config.enable_registration_captcha:
-        for flow in flows:
-            flow.insert(0, LoginType.RECAPTCHA)
-
-    return flows
-
-
-def register_servlets(hs, http_server):
-    EmailRegisterRequestTokenRestServlet(hs).register(http_server)
-    MsisdnRegisterRequestTokenRestServlet(hs).register(http_server)
-    UsernameAvailabilityRestServlet(hs).register(http_server)
-    RegistrationSubmitTokenServlet(hs).register(http_server)
-    RegisterRestServlet(hs).register(http_server)
diff --git a/synapse/rest/client/v2_alpha/relations.py b/synapse/rest/client/v2_alpha/relations.py
deleted file mode 100644
index 0821cd285f..0000000000
--- a/synapse/rest/client/v2_alpha/relations.py
+++ /dev/null
@@ -1,381 +0,0 @@
-# Copyright 2019 New Vector Ltd
-#
-# 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.
-
-"""This class implements the proposed relation APIs from MSC 1849.
-
-Since the MSC has not been approved all APIs here are unstable and may change at
-any time to reflect changes in the MSC.
-"""
-
-import logging
-
-from synapse.api.constants import EventTypes, RelationTypes
-from synapse.api.errors import ShadowBanError, SynapseError
-from synapse.http.servlet import (
-    RestServlet,
-    parse_integer,
-    parse_json_object_from_request,
-    parse_string,
-)
-from synapse.rest.client.transactions import HttpTransactionCache
-from synapse.storage.relations import (
-    AggregationPaginationToken,
-    PaginationChunk,
-    RelationPaginationToken,
-)
-from synapse.util.stringutils import random_string
-
-from ._base import client_patterns
-
-logger = logging.getLogger(__name__)
-
-
-class RelationSendServlet(RestServlet):
-    """Helper API for sending events that have relation data.
-
-    Example API shape to send a 👍 reaction to a room:
-
-        POST /rooms/!foo/send_relation/$bar/m.annotation/m.reaction?key=%F0%9F%91%8D
-        {}
-
-        {
-            "event_id": "$foobar"
-        }
-    """
-
-    PATTERN = (
-        "/rooms/(?P<room_id>[^/]*)/send_relation"
-        "/(?P<parent_id>[^/]*)/(?P<relation_type>[^/]*)/(?P<event_type>[^/]*)"
-    )
-
-    def __init__(self, hs):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.event_creation_handler = hs.get_event_creation_handler()
-        self.txns = HttpTransactionCache(hs)
-
-    def register(self, http_server):
-        http_server.register_paths(
-            "POST",
-            client_patterns(self.PATTERN + "$", releases=()),
-            self.on_PUT_or_POST,
-            self.__class__.__name__,
-        )
-        http_server.register_paths(
-            "PUT",
-            client_patterns(self.PATTERN + "/(?P<txn_id>[^/]*)$", releases=()),
-            self.on_PUT,
-            self.__class__.__name__,
-        )
-
-    def on_PUT(self, request, *args, **kwargs):
-        return self.txns.fetch_or_execute_request(
-            request, self.on_PUT_or_POST, request, *args, **kwargs
-        )
-
-    async def on_PUT_or_POST(
-        self, request, room_id, parent_id, relation_type, event_type, txn_id=None
-    ):
-        requester = await self.auth.get_user_by_req(request, allow_guest=True)
-
-        if event_type == EventTypes.Member:
-            # Add relations to a membership is meaningless, so we just deny it
-            # at the CS API rather than trying to handle it correctly.
-            raise SynapseError(400, "Cannot send member events with relations")
-
-        content = parse_json_object_from_request(request)
-
-        aggregation_key = parse_string(request, "key", encoding="utf-8")
-
-        content["m.relates_to"] = {
-            "event_id": parent_id,
-            "key": aggregation_key,
-            "rel_type": relation_type,
-        }
-
-        event_dict = {
-            "type": event_type,
-            "content": content,
-            "room_id": room_id,
-            "sender": requester.user.to_string(),
-        }
-
-        try:
-            (
-                event,
-                _,
-            ) = await self.event_creation_handler.create_and_send_nonmember_event(
-                requester, event_dict=event_dict, txn_id=txn_id
-            )
-            event_id = event.event_id
-        except ShadowBanError:
-            event_id = "$" + random_string(43)
-
-        return 200, {"event_id": event_id}
-
-
-class RelationPaginationServlet(RestServlet):
-    """API to paginate relations on an event by topological ordering, optionally
-    filtered by relation type and event type.
-    """
-
-    PATTERNS = client_patterns(
-        "/rooms/(?P<room_id>[^/]*)/relations/(?P<parent_id>[^/]*)"
-        "(/(?P<relation_type>[^/]*)(/(?P<event_type>[^/]*))?)?$",
-        releases=(),
-    )
-
-    def __init__(self, hs):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.store = hs.get_datastore()
-        self.clock = hs.get_clock()
-        self._event_serializer = hs.get_event_client_serializer()
-        self.event_handler = hs.get_event_handler()
-
-    async def on_GET(
-        self, request, room_id, parent_id, relation_type=None, event_type=None
-    ):
-        requester = await self.auth.get_user_by_req(request, allow_guest=True)
-
-        await self.auth.check_user_in_room_or_world_readable(
-            room_id, requester.user.to_string(), allow_departed_users=True
-        )
-
-        # This gets the original event and checks that a) the event exists and
-        # b) the user is allowed to view it.
-        event = await self.event_handler.get_event(requester.user, room_id, parent_id)
-
-        limit = parse_integer(request, "limit", default=5)
-        from_token_str = parse_string(request, "from")
-        to_token_str = parse_string(request, "to")
-
-        if event.internal_metadata.is_redacted():
-            # If the event is redacted, return an empty list of relations
-            pagination_chunk = PaginationChunk(chunk=[])
-        else:
-            # Return the relations
-            from_token = None
-            if from_token_str:
-                from_token = RelationPaginationToken.from_string(from_token_str)
-
-            to_token = None
-            if to_token_str:
-                to_token = RelationPaginationToken.from_string(to_token_str)
-
-            pagination_chunk = await self.store.get_relations_for_event(
-                event_id=parent_id,
-                relation_type=relation_type,
-                event_type=event_type,
-                limit=limit,
-                from_token=from_token,
-                to_token=to_token,
-            )
-
-        events = await self.store.get_events_as_list(
-            [c["event_id"] for c in pagination_chunk.chunk]
-        )
-
-        now = self.clock.time_msec()
-        # We set bundle_aggregations to False when retrieving the original
-        # event because we want the content before relations were applied to
-        # it.
-        original_event = await self._event_serializer.serialize_event(
-            event, now, bundle_aggregations=False
-        )
-        # Similarly, we don't allow relations to be applied to relations, so we
-        # return the original relations without any aggregations on top of them
-        # here.
-        events = await self._event_serializer.serialize_events(
-            events, now, bundle_aggregations=False
-        )
-
-        return_value = pagination_chunk.to_dict()
-        return_value["chunk"] = events
-        return_value["original_event"] = original_event
-
-        return 200, return_value
-
-
-class RelationAggregationPaginationServlet(RestServlet):
-    """API to paginate aggregation groups of relations, e.g. paginate the
-    types and counts of the reactions on the events.
-
-    Example request and response:
-
-        GET /rooms/{room_id}/aggregations/{parent_id}
-
-        {
-            chunk: [
-                {
-                    "type": "m.reaction",
-                    "key": "👍",
-                    "count": 3
-                }
-            ]
-        }
-    """
-
-    PATTERNS = client_patterns(
-        "/rooms/(?P<room_id>[^/]*)/aggregations/(?P<parent_id>[^/]*)"
-        "(/(?P<relation_type>[^/]*)(/(?P<event_type>[^/]*))?)?$",
-        releases=(),
-    )
-
-    def __init__(self, hs):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.store = hs.get_datastore()
-        self.event_handler = hs.get_event_handler()
-
-    async def on_GET(
-        self, request, room_id, parent_id, relation_type=None, event_type=None
-    ):
-        requester = await self.auth.get_user_by_req(request, allow_guest=True)
-
-        await self.auth.check_user_in_room_or_world_readable(
-            room_id,
-            requester.user.to_string(),
-            allow_departed_users=True,
-        )
-
-        # This checks that a) the event exists and b) the user is allowed to
-        # view it.
-        event = await self.event_handler.get_event(requester.user, room_id, parent_id)
-
-        if relation_type not in (RelationTypes.ANNOTATION, None):
-            raise SynapseError(400, "Relation type must be 'annotation'")
-
-        limit = parse_integer(request, "limit", default=5)
-        from_token_str = parse_string(request, "from")
-        to_token_str = parse_string(request, "to")
-
-        if event.internal_metadata.is_redacted():
-            # If the event is redacted, return an empty list of relations
-            pagination_chunk = PaginationChunk(chunk=[])
-        else:
-            # Return the relations
-            from_token = None
-            if from_token_str:
-                from_token = AggregationPaginationToken.from_string(from_token_str)
-
-            to_token = None
-            if to_token_str:
-                to_token = AggregationPaginationToken.from_string(to_token_str)
-
-            pagination_chunk = await self.store.get_aggregation_groups_for_event(
-                event_id=parent_id,
-                event_type=event_type,
-                limit=limit,
-                from_token=from_token,
-                to_token=to_token,
-            )
-
-        return 200, pagination_chunk.to_dict()
-
-
-class RelationAggregationGroupPaginationServlet(RestServlet):
-    """API to paginate within an aggregation group of relations, e.g. paginate
-    all the 👍 reactions on an event.
-
-    Example request and response:
-
-        GET /rooms/{room_id}/aggregations/{parent_id}/m.annotation/m.reaction/👍
-
-        {
-            chunk: [
-                {
-                    "type": "m.reaction",
-                    "content": {
-                        "m.relates_to": {
-                            "rel_type": "m.annotation",
-                            "key": "👍"
-                        }
-                    }
-                },
-                ...
-            ]
-        }
-    """
-
-    PATTERNS = client_patterns(
-        "/rooms/(?P<room_id>[^/]*)/aggregations/(?P<parent_id>[^/]*)"
-        "/(?P<relation_type>[^/]*)/(?P<event_type>[^/]*)/(?P<key>[^/]*)$",
-        releases=(),
-    )
-
-    def __init__(self, hs):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.store = hs.get_datastore()
-        self.clock = hs.get_clock()
-        self._event_serializer = hs.get_event_client_serializer()
-        self.event_handler = hs.get_event_handler()
-
-    async def on_GET(self, request, room_id, parent_id, relation_type, event_type, key):
-        requester = await self.auth.get_user_by_req(request, allow_guest=True)
-
-        await self.auth.check_user_in_room_or_world_readable(
-            room_id,
-            requester.user.to_string(),
-            allow_departed_users=True,
-        )
-
-        # This checks that a) the event exists and b) the user is allowed to
-        # view it.
-        await self.event_handler.get_event(requester.user, room_id, parent_id)
-
-        if relation_type != RelationTypes.ANNOTATION:
-            raise SynapseError(400, "Relation type must be 'annotation'")
-
-        limit = parse_integer(request, "limit", default=5)
-        from_token_str = parse_string(request, "from")
-        to_token_str = parse_string(request, "to")
-
-        from_token = None
-        if from_token_str:
-            from_token = RelationPaginationToken.from_string(from_token_str)
-
-        to_token = None
-        if to_token_str:
-            to_token = RelationPaginationToken.from_string(to_token_str)
-
-        result = await self.store.get_relations_for_event(
-            event_id=parent_id,
-            relation_type=relation_type,
-            event_type=event_type,
-            aggregation_key=key,
-            limit=limit,
-            from_token=from_token,
-            to_token=to_token,
-        )
-
-        events = await self.store.get_events_as_list(
-            [c["event_id"] for c in result.chunk]
-        )
-
-        now = self.clock.time_msec()
-        events = await self._event_serializer.serialize_events(events, now)
-
-        return_value = result.to_dict()
-        return_value["chunk"] = events
-
-        return 200, return_value
-
-
-def register_servlets(hs, http_server):
-    RelationSendServlet(hs).register(http_server)
-    RelationPaginationServlet(hs).register(http_server)
-    RelationAggregationPaginationServlet(hs).register(http_server)
-    RelationAggregationGroupPaginationServlet(hs).register(http_server)
diff --git a/synapse/rest/client/v2_alpha/report_event.py b/synapse/rest/client/v2_alpha/report_event.py
deleted file mode 100644
index 07ea39a8a3..0000000000
--- a/synapse/rest/client/v2_alpha/report_event.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# Copyright 2016 OpenMarket Ltd
-#
-# 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.
-
-import logging
-from http import HTTPStatus
-
-from synapse.api.errors import Codes, SynapseError
-from synapse.http.servlet import RestServlet, parse_json_object_from_request
-
-from ._base import client_patterns
-
-logger = logging.getLogger(__name__)
-
-
-class ReportEventRestServlet(RestServlet):
-    PATTERNS = client_patterns("/rooms/(?P<room_id>[^/]*)/report/(?P<event_id>[^/]*)$")
-
-    def __init__(self, hs):
-        super().__init__()
-        self.hs = hs
-        self.auth = hs.get_auth()
-        self.clock = hs.get_clock()
-        self.store = hs.get_datastore()
-
-    async def on_POST(self, request, room_id, event_id):
-        requester = await self.auth.get_user_by_req(request)
-        user_id = requester.user.to_string()
-
-        body = parse_json_object_from_request(request)
-
-        if not isinstance(body.get("reason", ""), str):
-            raise SynapseError(
-                HTTPStatus.BAD_REQUEST,
-                "Param 'reason' must be a string",
-                Codes.BAD_JSON,
-            )
-        if not isinstance(body.get("score", 0), int):
-            raise SynapseError(
-                HTTPStatus.BAD_REQUEST,
-                "Param 'score' must be an integer",
-                Codes.BAD_JSON,
-            )
-
-        await self.store.add_event_report(
-            room_id=room_id,
-            event_id=event_id,
-            user_id=user_id,
-            reason=body.get("reason"),
-            content=body,
-            received_ts=self.clock.time_msec(),
-        )
-
-        return 200, {}
-
-
-def register_servlets(hs, http_server):
-    ReportEventRestServlet(hs).register(http_server)
diff --git a/synapse/rest/client/v2_alpha/room.py b/synapse/rest/client/v2_alpha/room.py
deleted file mode 100644
index 3172aba605..0000000000
--- a/synapse/rest/client/v2_alpha/room.py
+++ /dev/null
@@ -1,441 +0,0 @@
-# Copyright 2016 OpenMarket Ltd
-#
-# 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.
-
-import logging
-import re
-
-from synapse.api.constants import EventContentFields, EventTypes
-from synapse.api.errors import AuthError, Codes, SynapseError
-from synapse.appservice import ApplicationService
-from synapse.http.servlet import (
-    RestServlet,
-    assert_params_in_dict,
-    parse_json_object_from_request,
-    parse_string,
-    parse_strings_from_args,
-)
-from synapse.rest.client.transactions import HttpTransactionCache
-from synapse.types import Requester, UserID, create_requester
-from synapse.util.stringutils import random_string
-
-logger = logging.getLogger(__name__)
-
-
-class RoomBatchSendEventRestServlet(RestServlet):
-    """
-    API endpoint which can insert a chunk of events historically back in time
-    next to the given `prev_event`.
-
-    `chunk_id` comes from `next_chunk_id `in the response of the batch send
-    endpoint and is derived from the "insertion" events added to each chunk.
-    It's not required for the first batch send.
-
-    `state_events_at_start` is used to define the historical state events
-    needed to auth the events like join events. These events will float
-    outside of the normal DAG as outlier's and won't be visible in the chat
-    history which also allows us to insert multiple chunks without having a bunch
-    of `@mxid joined the room` noise between each chunk.
-
-    `events` is chronological chunk/list of events you want to insert.
-    There is a reverse-chronological constraint on chunks so once you insert
-    some messages, you can only insert older ones after that.
-    tldr; Insert chunks from your most recent history -> oldest history.
-
-    POST /_matrix/client/unstable/org.matrix.msc2716/rooms/<roomID>/batch_send?prev_event=<eventID>&chunk_id=<chunkID>
-    {
-        "events": [ ... ],
-        "state_events_at_start": [ ... ]
-    }
-    """
-
-    PATTERNS = (
-        re.compile(
-            "^/_matrix/client/unstable/org.matrix.msc2716"
-            "/rooms/(?P<room_id>[^/]*)/batch_send$"
-        ),
-    )
-
-    def __init__(self, hs):
-        super().__init__()
-        self.hs = hs
-        self.store = hs.get_datastore()
-        self.state_store = hs.get_storage().state
-        self.event_creation_handler = hs.get_event_creation_handler()
-        self.room_member_handler = hs.get_room_member_handler()
-        self.auth = hs.get_auth()
-        self.txns = HttpTransactionCache(hs)
-
-    async def _inherit_depth_from_prev_ids(self, prev_event_ids) -> int:
-        (
-            most_recent_prev_event_id,
-            most_recent_prev_event_depth,
-        ) = await self.store.get_max_depth_of(prev_event_ids)
-
-        # We want to insert the historical event after the `prev_event` but before the successor event
-        #
-        # We inherit depth from the successor event instead of the `prev_event`
-        # because events returned from `/messages` are first sorted by `topological_ordering`
-        # which is just the `depth` and then tie-break with `stream_ordering`.
-        #
-        # We mark these inserted historical events as "backfilled" which gives them a
-        # negative `stream_ordering`. If we use the same depth as the `prev_event`,
-        # then our historical event will tie-break and be sorted before the `prev_event`
-        # when it should come after.
-        #
-        # We want to use the successor event depth so they appear after `prev_event` because
-        # it has a larger `depth` but before the successor event because the `stream_ordering`
-        # is negative before the successor event.
-        successor_event_ids = await self.store.get_successor_events(
-            [most_recent_prev_event_id]
-        )
-
-        # If we can't find any successor events, then it's a forward extremity of
-        # historical messages and we can just inherit from the previous historical
-        # event which we can already assume has the correct depth where we want
-        # to insert into.
-        if not successor_event_ids:
-            depth = most_recent_prev_event_depth
-        else:
-            (
-                _,
-                oldest_successor_depth,
-            ) = await self.store.get_min_depth_of(successor_event_ids)
-
-            depth = oldest_successor_depth
-
-        return depth
-
-    def _create_insertion_event_dict(
-        self, sender: str, room_id: str, origin_server_ts: int
-    ):
-        """Creates an event dict for an "insertion" event with the proper fields
-        and a random chunk ID.
-
-        Args:
-            sender: The event author MXID
-            room_id: The room ID that the event belongs to
-            origin_server_ts: Timestamp when the event was sent
-
-        Returns:
-            Tuple of event ID and stream ordering position
-        """
-
-        next_chunk_id = random_string(8)
-        insertion_event = {
-            "type": EventTypes.MSC2716_INSERTION,
-            "sender": sender,
-            "room_id": room_id,
-            "content": {
-                EventContentFields.MSC2716_NEXT_CHUNK_ID: next_chunk_id,
-                EventContentFields.MSC2716_HISTORICAL: True,
-            },
-            "origin_server_ts": origin_server_ts,
-        }
-
-        return insertion_event
-
-    async def _create_requester_for_user_id_from_app_service(
-        self, user_id: str, app_service: ApplicationService
-    ) -> Requester:
-        """Creates a new requester for the given user_id
-        and validates that the app service is allowed to control
-        the given user.
-
-        Args:
-            user_id: The author MXID that the app service is controlling
-            app_service: The app service that controls the user
-
-        Returns:
-            Requester object
-        """
-
-        await self.auth.validate_appservice_can_control_user_id(app_service, user_id)
-
-        return create_requester(user_id, app_service=app_service)
-
-    async def on_POST(self, request, room_id):
-        requester = await self.auth.get_user_by_req(request, allow_guest=False)
-
-        if not requester.app_service:
-            raise AuthError(
-                403,
-                "Only application services can use the /batchsend endpoint",
-            )
-
-        body = parse_json_object_from_request(request)
-        assert_params_in_dict(body, ["state_events_at_start", "events"])
-
-        prev_events_from_query = parse_strings_from_args(request.args, "prev_event")
-        chunk_id_from_query = parse_string(request, "chunk_id")
-
-        if prev_events_from_query is None:
-            raise SynapseError(
-                400,
-                "prev_event query parameter is required when inserting historical messages back in time",
-                errcode=Codes.MISSING_PARAM,
-            )
-
-        # For the event we are inserting next to (`prev_events_from_query`),
-        # find the most recent auth events (derived from state events) that
-        # allowed that message to be sent. We will use that as a base
-        # to auth our historical messages against.
-        (
-            most_recent_prev_event_id,
-            _,
-        ) = await self.store.get_max_depth_of(prev_events_from_query)
-        # mapping from (type, state_key) -> state_event_id
-        prev_state_map = await self.state_store.get_state_ids_for_event(
-            most_recent_prev_event_id
-        )
-        # List of state event ID's
-        prev_state_ids = list(prev_state_map.values())
-        auth_event_ids = prev_state_ids
-
-        state_events_at_start = []
-        for state_event in body["state_events_at_start"]:
-            assert_params_in_dict(
-                state_event, ["type", "origin_server_ts", "content", "sender"]
-            )
-
-            logger.debug(
-                "RoomBatchSendEventRestServlet inserting state_event=%s, auth_event_ids=%s",
-                state_event,
-                auth_event_ids,
-            )
-
-            event_dict = {
-                "type": state_event["type"],
-                "origin_server_ts": state_event["origin_server_ts"],
-                "content": state_event["content"],
-                "room_id": room_id,
-                "sender": state_event["sender"],
-                "state_key": state_event["state_key"],
-            }
-
-            # Mark all events as historical
-            event_dict["content"][EventContentFields.MSC2716_HISTORICAL] = True
-
-            # Make the state events float off on their own
-            fake_prev_event_id = "$" + random_string(43)
-
-            # TODO: This is pretty much the same as some other code to handle inserting state in this file
-            if event_dict["type"] == EventTypes.Member:
-                membership = event_dict["content"].get("membership", None)
-                event_id, _ = await self.room_member_handler.update_membership(
-                    await self._create_requester_for_user_id_from_app_service(
-                        state_event["sender"], requester.app_service
-                    ),
-                    target=UserID.from_string(event_dict["state_key"]),
-                    room_id=room_id,
-                    action=membership,
-                    content=event_dict["content"],
-                    outlier=True,
-                    prev_event_ids=[fake_prev_event_id],
-                    # Make sure to use a copy of this list because we modify it
-                    # later in the loop here. Otherwise it will be the same
-                    # reference and also update in the event when we append later.
-                    auth_event_ids=auth_event_ids.copy(),
-                )
-            else:
-                # TODO: Add some complement tests that adds state that is not member joins
-                # and will use this code path. Maybe we only want to support join state events
-                # and can get rid of this `else`?
-                (
-                    event,
-                    _,
-                ) = await self.event_creation_handler.create_and_send_nonmember_event(
-                    await self._create_requester_for_user_id_from_app_service(
-                        state_event["sender"], requester.app_service
-                    ),
-                    event_dict,
-                    outlier=True,
-                    prev_event_ids=[fake_prev_event_id],
-                    # Make sure to use a copy of this list because we modify it
-                    # later in the loop here. Otherwise it will be the same
-                    # reference and also update in the event when we append later.
-                    auth_event_ids=auth_event_ids.copy(),
-                )
-                event_id = event.event_id
-
-            state_events_at_start.append(event_id)
-            auth_event_ids.append(event_id)
-
-        events_to_create = body["events"]
-
-        inherited_depth = await self._inherit_depth_from_prev_ids(
-            prev_events_from_query
-        )
-
-        # Figure out which chunk to connect to. If they passed in
-        # chunk_id_from_query let's use it. The chunk ID passed in comes
-        # from the chunk_id in the "insertion" event from the previous chunk.
-        last_event_in_chunk = events_to_create[-1]
-        chunk_id_to_connect_to = chunk_id_from_query
-        base_insertion_event = None
-        if chunk_id_from_query:
-            #  All but the first base insertion event should point at a fake
-            #  event, which causes the HS to ask for the state at the start of
-            #  the chunk later.
-            prev_event_ids = [fake_prev_event_id]
-            # TODO: Verify the chunk_id_from_query corresponds to an insertion event
-            pass
-        # Otherwise, create an insertion event to act as a starting point.
-        #
-        # We don't always have an insertion event to start hanging more history
-        # off of (ideally there would be one in the main DAG, but that's not the
-        # case if we're wanting to add history to e.g. existing rooms without
-        # an insertion event), in which case we just create a new insertion event
-        # that can then get pointed to by a "marker" event later.
-        else:
-            prev_event_ids = prev_events_from_query
-
-            base_insertion_event_dict = self._create_insertion_event_dict(
-                sender=requester.user.to_string(),
-                room_id=room_id,
-                origin_server_ts=last_event_in_chunk["origin_server_ts"],
-            )
-            base_insertion_event_dict["prev_events"] = prev_event_ids.copy()
-
-            (
-                base_insertion_event,
-                _,
-            ) = await self.event_creation_handler.create_and_send_nonmember_event(
-                await self._create_requester_for_user_id_from_app_service(
-                    base_insertion_event_dict["sender"],
-                    requester.app_service,
-                ),
-                base_insertion_event_dict,
-                prev_event_ids=base_insertion_event_dict.get("prev_events"),
-                auth_event_ids=auth_event_ids,
-                historical=True,
-                depth=inherited_depth,
-            )
-
-            chunk_id_to_connect_to = base_insertion_event["content"][
-                EventContentFields.MSC2716_NEXT_CHUNK_ID
-            ]
-
-        # Connect this current chunk to the insertion event from the previous chunk
-        chunk_event = {
-            "type": EventTypes.MSC2716_CHUNK,
-            "sender": requester.user.to_string(),
-            "room_id": room_id,
-            "content": {
-                EventContentFields.MSC2716_CHUNK_ID: chunk_id_to_connect_to,
-                EventContentFields.MSC2716_HISTORICAL: True,
-            },
-            # Since the chunk event is put at the end of the chunk,
-            # where the newest-in-time event is, copy the origin_server_ts from
-            # the last event we're inserting
-            "origin_server_ts": last_event_in_chunk["origin_server_ts"],
-        }
-        # Add the chunk event to the end of the chunk (newest-in-time)
-        events_to_create.append(chunk_event)
-
-        # Add an "insertion" event to the start of each chunk (next to the oldest-in-time
-        # event in the chunk) so the next chunk can be connected to this one.
-        insertion_event = self._create_insertion_event_dict(
-            sender=requester.user.to_string(),
-            room_id=room_id,
-            # Since the insertion event is put at the start of the chunk,
-            # where the oldest-in-time event is, copy the origin_server_ts from
-            # the first event we're inserting
-            origin_server_ts=events_to_create[0]["origin_server_ts"],
-        )
-        # Prepend the insertion event to the start of the chunk (oldest-in-time)
-        events_to_create = [insertion_event] + events_to_create
-
-        event_ids = []
-        events_to_persist = []
-        for ev in events_to_create:
-            assert_params_in_dict(ev, ["type", "origin_server_ts", "content", "sender"])
-
-            event_dict = {
-                "type": ev["type"],
-                "origin_server_ts": ev["origin_server_ts"],
-                "content": ev["content"],
-                "room_id": room_id,
-                "sender": ev["sender"],  # requester.user.to_string(),
-                "prev_events": prev_event_ids.copy(),
-            }
-
-            # Mark all events as historical
-            event_dict["content"][EventContentFields.MSC2716_HISTORICAL] = True
-
-            event, context = await self.event_creation_handler.create_event(
-                await self._create_requester_for_user_id_from_app_service(
-                    ev["sender"], requester.app_service
-                ),
-                event_dict,
-                prev_event_ids=event_dict.get("prev_events"),
-                auth_event_ids=auth_event_ids,
-                historical=True,
-                depth=inherited_depth,
-            )
-            logger.debug(
-                "RoomBatchSendEventRestServlet inserting event=%s, prev_event_ids=%s, auth_event_ids=%s",
-                event,
-                prev_event_ids,
-                auth_event_ids,
-            )
-
-            assert self.hs.is_mine_id(event.sender), "User must be our own: %s" % (
-                event.sender,
-            )
-
-            events_to_persist.append((event, context))
-            event_id = event.event_id
-
-            event_ids.append(event_id)
-            prev_event_ids = [event_id]
-
-        # Persist events in reverse-chronological order so they have the
-        # correct stream_ordering as they are backfilled (which decrements).
-        # Events are sorted by (topological_ordering, stream_ordering)
-        # where topological_ordering is just depth.
-        for (event, context) in reversed(events_to_persist):
-            ev = await self.event_creation_handler.handle_new_client_event(
-                await self._create_requester_for_user_id_from_app_service(
-                    event["sender"], requester.app_service
-                ),
-                event=event,
-                context=context,
-            )
-
-        # Add the base_insertion_event to the bottom of the list we return
-        if base_insertion_event is not None:
-            event_ids.append(base_insertion_event.event_id)
-
-        return 200, {
-            "state_events": state_events_at_start,
-            "events": event_ids,
-            "next_chunk_id": insertion_event["content"][
-                EventContentFields.MSC2716_NEXT_CHUNK_ID
-            ],
-        }
-
-    def on_GET(self, request, room_id):
-        return 501, "Not implemented"
-
-    def on_PUT(self, request, room_id):
-        return self.txns.fetch_or_execute_request(
-            request, self.on_POST, request, room_id
-        )
-
-
-def register_servlets(hs, http_server):
-    msc2716_enabled = hs.config.experimental.msc2716_enabled
-
-    if msc2716_enabled:
-        RoomBatchSendEventRestServlet(hs).register(http_server)
diff --git a/synapse/rest/client/v2_alpha/room_keys.py b/synapse/rest/client/v2_alpha/room_keys.py
deleted file mode 100644
index 263596be86..0000000000
--- a/synapse/rest/client/v2_alpha/room_keys.py
+++ /dev/null
@@ -1,391 +0,0 @@
-# Copyright 2017, 2018 New Vector Ltd
-#
-# 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.
-
-import logging
-
-from synapse.api.errors import Codes, NotFoundError, SynapseError
-from synapse.http.servlet import (
-    RestServlet,
-    parse_json_object_from_request,
-    parse_string,
-)
-
-from ._base import client_patterns
-
-logger = logging.getLogger(__name__)
-
-
-class RoomKeysServlet(RestServlet):
-    PATTERNS = client_patterns(
-        "/room_keys/keys(/(?P<room_id>[^/]+))?(/(?P<session_id>[^/]+))?$"
-    )
-
-    def __init__(self, hs):
-        """
-        Args:
-            hs (synapse.server.HomeServer): server
-        """
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.e2e_room_keys_handler = hs.get_e2e_room_keys_handler()
-
-    async def on_PUT(self, request, room_id, session_id):
-        """
-        Uploads one or more encrypted E2E room keys for backup purposes.
-        room_id: the ID of the room the keys are for (optional)
-        session_id: the ID for the E2E room keys for the room (optional)
-        version: the version of the user's backup which this data is for.
-        the version must already have been created via the /room_keys/version API.
-
-        Each session has:
-         * first_message_index: a numeric index indicating the oldest message
-           encrypted by this session.
-         * forwarded_count: how many times the uploading client claims this key
-           has been shared (forwarded)
-         * is_verified: whether the client that uploaded the keys claims they
-           were sent by a device which they've verified
-         * session_data: base64-encrypted data describing the session.
-
-        Returns 200 OK on success with body {}
-        Returns 403 Forbidden if the version in question is not the most recently
-        created version (i.e. if this is an old client trying to write to a stale backup)
-        Returns 404 Not Found if the version in question doesn't exist
-
-        The API is designed to be otherwise agnostic to the room_key encryption
-        algorithm being used.  Sessions are merged with existing ones in the
-        backup using the heuristics:
-         * is_verified sessions always win over unverified sessions
-         * older first_message_index always win over newer sessions
-         * lower forwarded_count always wins over higher forwarded_count
-
-        We trust the clients not to lie and corrupt their own backups.
-        It also means that if your access_token is stolen, the attacker could
-        delete your backup.
-
-        POST /room_keys/keys/!abc:matrix.org/c0ff33?version=1 HTTP/1.1
-        Content-Type: application/json
-
-        {
-            "first_message_index": 1,
-            "forwarded_count": 1,
-            "is_verified": false,
-            "session_data": "SSBBTSBBIEZJU0gK"
-        }
-
-        Or...
-
-        POST /room_keys/keys/!abc:matrix.org?version=1 HTTP/1.1
-        Content-Type: application/json
-
-        {
-            "sessions": {
-                "c0ff33": {
-                    "first_message_index": 1,
-                    "forwarded_count": 1,
-                    "is_verified": false,
-                    "session_data": "SSBBTSBBIEZJU0gK"
-                }
-            }
-        }
-
-        Or...
-
-        POST /room_keys/keys?version=1 HTTP/1.1
-        Content-Type: application/json
-
-        {
-            "rooms": {
-                "!abc:matrix.org": {
-                    "sessions": {
-                        "c0ff33": {
-                            "first_message_index": 1,
-                            "forwarded_count": 1,
-                            "is_verified": false,
-                            "session_data": "SSBBTSBBIEZJU0gK"
-                        }
-                    }
-                }
-            }
-        }
-        """
-        requester = await self.auth.get_user_by_req(request, allow_guest=False)
-        user_id = requester.user.to_string()
-        body = parse_json_object_from_request(request)
-        version = parse_string(request, "version")
-
-        if session_id:
-            body = {"sessions": {session_id: body}}
-
-        if room_id:
-            body = {"rooms": {room_id: body}}
-
-        ret = await self.e2e_room_keys_handler.upload_room_keys(user_id, version, body)
-        return 200, ret
-
-    async def on_GET(self, request, room_id, session_id):
-        """
-        Retrieves one or more encrypted E2E room keys for backup purposes.
-        Symmetric with the PUT version of the API.
-
-        room_id: the ID of the room to retrieve the keys for (optional)
-        session_id: the ID for the E2E room keys to retrieve the keys for (optional)
-        version: the version of the user's backup which this data is for.
-        the version must already have been created via the /change_secret API.
-
-        Returns as follows:
-
-        GET /room_keys/keys/!abc:matrix.org/c0ff33?version=1 HTTP/1.1
-        {
-            "first_message_index": 1,
-            "forwarded_count": 1,
-            "is_verified": false,
-            "session_data": "SSBBTSBBIEZJU0gK"
-        }
-
-        Or...
-
-        GET /room_keys/keys/!abc:matrix.org?version=1 HTTP/1.1
-        {
-            "sessions": {
-                "c0ff33": {
-                    "first_message_index": 1,
-                    "forwarded_count": 1,
-                    "is_verified": false,
-                    "session_data": "SSBBTSBBIEZJU0gK"
-                }
-            }
-        }
-
-        Or...
-
-        GET /room_keys/keys?version=1 HTTP/1.1
-        {
-            "rooms": {
-                "!abc:matrix.org": {
-                    "sessions": {
-                        "c0ff33": {
-                            "first_message_index": 1,
-                            "forwarded_count": 1,
-                            "is_verified": false,
-                            "session_data": "SSBBTSBBIEZJU0gK"
-                        }
-                    }
-                }
-            }
-        }
-        """
-        requester = await self.auth.get_user_by_req(request, allow_guest=False)
-        user_id = requester.user.to_string()
-        version = parse_string(request, "version", required=True)
-
-        room_keys = await self.e2e_room_keys_handler.get_room_keys(
-            user_id, version, room_id, session_id
-        )
-
-        # Convert room_keys to the right format to return.
-        if session_id:
-            # If the client requests a specific session, but that session was
-            # not backed up, then return an M_NOT_FOUND.
-            if room_keys["rooms"] == {}:
-                raise NotFoundError("No room_keys found")
-            else:
-                room_keys = room_keys["rooms"][room_id]["sessions"][session_id]
-        elif room_id:
-            # If the client requests all sessions from a room, but no sessions
-            # are found, then return an empty result rather than an error, so
-            # that clients don't have to handle an error condition, and an
-            # empty result is valid.  (Similarly if the client requests all
-            # sessions from the backup, but in that case, room_keys is already
-            # in the right format, so we don't need to do anything about it.)
-            if room_keys["rooms"] == {}:
-                room_keys = {"sessions": {}}
-            else:
-                room_keys = room_keys["rooms"][room_id]
-
-        return 200, room_keys
-
-    async def on_DELETE(self, request, room_id, session_id):
-        """
-        Deletes one or more encrypted E2E room keys for a user for backup purposes.
-
-        DELETE /room_keys/keys/!abc:matrix.org/c0ff33?version=1
-        HTTP/1.1 200 OK
-        {}
-
-        room_id: the ID of the room whose keys to delete (optional)
-        session_id: the ID for the E2E session to delete (optional)
-        version: the version of the user's backup which this data is for.
-        the version must already have been created via the /change_secret API.
-        """
-
-        requester = await self.auth.get_user_by_req(request, allow_guest=False)
-        user_id = requester.user.to_string()
-        version = parse_string(request, "version")
-
-        ret = await self.e2e_room_keys_handler.delete_room_keys(
-            user_id, version, room_id, session_id
-        )
-        return 200, ret
-
-
-class RoomKeysNewVersionServlet(RestServlet):
-    PATTERNS = client_patterns("/room_keys/version$")
-
-    def __init__(self, hs):
-        """
-        Args:
-            hs (synapse.server.HomeServer): server
-        """
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.e2e_room_keys_handler = hs.get_e2e_room_keys_handler()
-
-    async def on_POST(self, request):
-        """
-        Create a new backup version for this user's room_keys with the given
-        info.  The version is allocated by the server and returned to the user
-        in the response.  This API is intended to be used whenever the user
-        changes the encryption key for their backups, ensuring that backups
-        encrypted with different keys don't collide.
-
-        It takes out an exclusive lock on this user's room_key backups, to ensure
-        clients only upload to the current backup.
-
-        The algorithm passed in the version info is a reverse-DNS namespaced
-        identifier to describe the format of the encrypted backupped keys.
-
-        The auth_data is { user_id: "user_id", nonce: <random string> }
-        encrypted using the algorithm and current encryption key described above.
-
-        POST /room_keys/version
-        Content-Type: application/json
-        {
-            "algorithm": "m.megolm_backup.v1",
-            "auth_data": "dGhpcyBzaG91bGQgYWN0dWFsbHkgYmUgZW5jcnlwdGVkIGpzb24K"
-        }
-
-        HTTP/1.1 200 OK
-        Content-Type: application/json
-        {
-            "version": 12345
-        }
-        """
-        requester = await self.auth.get_user_by_req(request, allow_guest=False)
-        user_id = requester.user.to_string()
-        info = parse_json_object_from_request(request)
-
-        new_version = await self.e2e_room_keys_handler.create_version(user_id, info)
-        return 200, {"version": new_version}
-
-    # we deliberately don't have a PUT /version, as these things really should
-    # be immutable to avoid people footgunning
-
-
-class RoomKeysVersionServlet(RestServlet):
-    PATTERNS = client_patterns("/room_keys/version(/(?P<version>[^/]+))?$")
-
-    def __init__(self, hs):
-        """
-        Args:
-            hs (synapse.server.HomeServer): server
-        """
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.e2e_room_keys_handler = hs.get_e2e_room_keys_handler()
-
-    async def on_GET(self, request, version):
-        """
-        Retrieve the version information about a given version of the user's
-        room_keys backup.  If the version part is missing, returns info about the
-        most current backup version (if any)
-
-        It takes out an exclusive lock on this user's room_key backups, to ensure
-        clients only upload to the current backup.
-
-        Returns 404 if the given version does not exist.
-
-        GET /room_keys/version/12345 HTTP/1.1
-        {
-            "version": "12345",
-            "algorithm": "m.megolm_backup.v1",
-            "auth_data": "dGhpcyBzaG91bGQgYWN0dWFsbHkgYmUgZW5jcnlwdGVkIGpzb24K"
-        }
-        """
-        requester = await self.auth.get_user_by_req(request, allow_guest=False)
-        user_id = requester.user.to_string()
-
-        try:
-            info = await self.e2e_room_keys_handler.get_version_info(user_id, version)
-        except SynapseError as e:
-            if e.code == 404:
-                raise SynapseError(404, "No backup found", Codes.NOT_FOUND)
-        return 200, info
-
-    async def on_DELETE(self, request, version):
-        """
-        Delete the information about a given version of the user's
-        room_keys backup.  If the version part is missing, deletes the most
-        current backup version (if any). Doesn't delete the actual room data.
-
-        DELETE /room_keys/version/12345 HTTP/1.1
-        HTTP/1.1 200 OK
-        {}
-        """
-        if version is None:
-            raise SynapseError(400, "No version specified to delete", Codes.NOT_FOUND)
-
-        requester = await self.auth.get_user_by_req(request, allow_guest=False)
-        user_id = requester.user.to_string()
-
-        await self.e2e_room_keys_handler.delete_version(user_id, version)
-        return 200, {}
-
-    async def on_PUT(self, request, version):
-        """
-        Update the information about a given version of the user's room_keys backup.
-
-        POST /room_keys/version/12345 HTTP/1.1
-        Content-Type: application/json
-        {
-            "algorithm": "m.megolm_backup.v1",
-            "auth_data": {
-                "public_key": "abcdefg",
-                "signatures": {
-                    "ed25519:something": "hijklmnop"
-                }
-            },
-            "version": "12345"
-        }
-
-        HTTP/1.1 200 OK
-        Content-Type: application/json
-        {}
-        """
-        requester = await self.auth.get_user_by_req(request, allow_guest=False)
-        user_id = requester.user.to_string()
-        info = parse_json_object_from_request(request)
-
-        if version is None:
-            raise SynapseError(
-                400, "No version specified to update", Codes.MISSING_PARAM
-            )
-
-        await self.e2e_room_keys_handler.update_version(user_id, version, info)
-        return 200, {}
-
-
-def register_servlets(hs, http_server):
-    RoomKeysServlet(hs).register(http_server)
-    RoomKeysVersionServlet(hs).register(http_server)
-    RoomKeysNewVersionServlet(hs).register(http_server)
diff --git a/synapse/rest/client/v2_alpha/room_upgrade_rest_servlet.py b/synapse/rest/client/v2_alpha/room_upgrade_rest_servlet.py
deleted file mode 100644
index 6d1b083acb..0000000000
--- a/synapse/rest/client/v2_alpha/room_upgrade_rest_servlet.py
+++ /dev/null
@@ -1,88 +0,0 @@
-# Copyright 2016 OpenMarket Ltd
-#
-# 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.
-
-import logging
-
-from synapse.api.errors import Codes, ShadowBanError, SynapseError
-from synapse.api.room_versions import KNOWN_ROOM_VERSIONS
-from synapse.http.servlet import (
-    RestServlet,
-    assert_params_in_dict,
-    parse_json_object_from_request,
-)
-from synapse.util import stringutils
-
-from ._base import client_patterns
-
-logger = logging.getLogger(__name__)
-
-
-class RoomUpgradeRestServlet(RestServlet):
-    """Handler for room upgrade requests.
-
-    Handles requests of the form:
-
-        POST /_matrix/client/r0/rooms/$roomid/upgrade HTTP/1.1
-        Content-Type: application/json
-
-        {
-            "new_version": "2",
-        }
-
-    Creates a new room and shuts down the old one. Returns the ID of the new room.
-
-    Args:
-        hs (synapse.server.HomeServer):
-    """
-
-    PATTERNS = client_patterns(
-        # /rooms/$roomid/upgrade
-        "/rooms/(?P<room_id>[^/]*)/upgrade$"
-    )
-
-    def __init__(self, hs):
-        super().__init__()
-        self._hs = hs
-        self._room_creation_handler = hs.get_room_creation_handler()
-        self._auth = hs.get_auth()
-
-    async def on_POST(self, request, room_id):
-        requester = await self._auth.get_user_by_req(request)
-
-        content = parse_json_object_from_request(request)
-        assert_params_in_dict(content, ("new_version",))
-
-        new_version = KNOWN_ROOM_VERSIONS.get(content["new_version"])
-        if new_version is None:
-            raise SynapseError(
-                400,
-                "Your homeserver does not support this room version",
-                Codes.UNSUPPORTED_ROOM_VERSION,
-            )
-
-        try:
-            new_room_id = await self._room_creation_handler.upgrade_room(
-                requester, room_id, new_version
-            )
-        except ShadowBanError:
-            # Generate a random room ID.
-            new_room_id = stringutils.random_string(18)
-
-        ret = {"replacement_room": new_room_id}
-
-        return 200, ret
-
-
-def register_servlets(hs, http_server):
-    RoomUpgradeRestServlet(hs).register(http_server)
diff --git a/synapse/rest/client/v2_alpha/sendtodevice.py b/synapse/rest/client/v2_alpha/sendtodevice.py
deleted file mode 100644
index d537d811d8..0000000000
--- a/synapse/rest/client/v2_alpha/sendtodevice.py
+++ /dev/null
@@ -1,67 +0,0 @@
-# Copyright 2016 OpenMarket Ltd
-#
-# 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.
-
-import logging
-from typing import Tuple
-
-from synapse.http import servlet
-from synapse.http.servlet import assert_params_in_dict, parse_json_object_from_request
-from synapse.logging.opentracing import set_tag, trace
-from synapse.rest.client.transactions import HttpTransactionCache
-
-from ._base import client_patterns
-
-logger = logging.getLogger(__name__)
-
-
-class SendToDeviceRestServlet(servlet.RestServlet):
-    PATTERNS = client_patterns(
-        "/sendToDevice/(?P<message_type>[^/]*)/(?P<txn_id>[^/]*)$"
-    )
-
-    def __init__(self, hs):
-        """
-        Args:
-            hs (synapse.server.HomeServer): server
-        """
-        super().__init__()
-        self.hs = hs
-        self.auth = hs.get_auth()
-        self.txns = HttpTransactionCache(hs)
-        self.device_message_handler = hs.get_device_message_handler()
-
-    @trace(opname="sendToDevice")
-    def on_PUT(self, request, message_type, txn_id):
-        set_tag("message_type", message_type)
-        set_tag("txn_id", txn_id)
-        return self.txns.fetch_or_execute_request(
-            request, self._put, request, message_type, txn_id
-        )
-
-    async def _put(self, request, message_type, txn_id):
-        requester = await self.auth.get_user_by_req(request, allow_guest=True)
-
-        content = parse_json_object_from_request(request)
-        assert_params_in_dict(content, ("messages",))
-
-        await self.device_message_handler.send_device_message(
-            requester, message_type, content["messages"]
-        )
-
-        response: Tuple[int, dict] = (200, {})
-        return response
-
-
-def register_servlets(hs, http_server):
-    SendToDeviceRestServlet(hs).register(http_server)
diff --git a/synapse/rest/client/v2_alpha/shared_rooms.py b/synapse/rest/client/v2_alpha/shared_rooms.py
deleted file mode 100644
index d2e7f04b40..0000000000
--- a/synapse/rest/client/v2_alpha/shared_rooms.py
+++ /dev/null
@@ -1,67 +0,0 @@
-# Copyright 2020 Half-Shot
-#
-# 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.
-import logging
-
-from synapse.api.errors import Codes, SynapseError
-from synapse.http.servlet import RestServlet
-from synapse.types import UserID
-
-from ._base import client_patterns
-
-logger = logging.getLogger(__name__)
-
-
-class UserSharedRoomsServlet(RestServlet):
-    """
-    GET /uk.half-shot.msc2666/user/shared_rooms/{user_id} HTTP/1.1
-    """
-
-    PATTERNS = client_patterns(
-        "/uk.half-shot.msc2666/user/shared_rooms/(?P<user_id>[^/]*)",
-        releases=(),  # This is an unstable feature
-    )
-
-    def __init__(self, hs):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.store = hs.get_datastore()
-        self.user_directory_active = hs.config.update_user_directory
-
-    async def on_GET(self, request, user_id):
-
-        if not self.user_directory_active:
-            raise SynapseError(
-                code=400,
-                msg="The user directory is disabled on this server. Cannot determine shared rooms.",
-                errcode=Codes.FORBIDDEN,
-            )
-
-        UserID.from_string(user_id)
-
-        requester = await self.auth.get_user_by_req(request)
-        if user_id == requester.user.to_string():
-            raise SynapseError(
-                code=400,
-                msg="You cannot request a list of shared rooms with yourself",
-                errcode=Codes.FORBIDDEN,
-            )
-        rooms = await self.store.get_shared_rooms_for_users(
-            requester.user.to_string(), user_id
-        )
-
-        return 200, {"joined": list(rooms)}
-
-
-def register_servlets(hs, http_server):
-    UserSharedRoomsServlet(hs).register(http_server)
diff --git a/synapse/rest/client/v2_alpha/sync.py b/synapse/rest/client/v2_alpha/sync.py
deleted file mode 100644
index e18f4d01b3..0000000000
--- a/synapse/rest/client/v2_alpha/sync.py
+++ /dev/null
@@ -1,532 +0,0 @@
-# Copyright 2015, 2016 OpenMarket Ltd
-#
-# 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.
-import itertools
-import logging
-from collections import defaultdict
-from typing import TYPE_CHECKING, Any, Callable, Dict, List, Tuple
-
-from synapse.api.constants import Membership, PresenceState
-from synapse.api.errors import Codes, StoreError, SynapseError
-from synapse.api.filtering import DEFAULT_FILTER_COLLECTION, FilterCollection
-from synapse.events.utils import (
-    format_event_for_client_v2_without_room_id,
-    format_event_raw,
-)
-from synapse.handlers.presence import format_user_presence_state
-from synapse.handlers.sync import KnockedSyncResult, SyncConfig
-from synapse.http.servlet import RestServlet, parse_boolean, parse_integer, parse_string
-from synapse.http.site import SynapseRequest
-from synapse.types import JsonDict, StreamToken
-from synapse.util import json_decoder
-
-from ._base import client_patterns, set_timeline_upper_limit
-
-if TYPE_CHECKING:
-    from synapse.server import HomeServer
-
-logger = logging.getLogger(__name__)
-
-
-class SyncRestServlet(RestServlet):
-    """
-
-    GET parameters::
-        timeout(int): How long to wait for new events in milliseconds.
-        since(batch_token): Batch token when asking for incremental deltas.
-        set_presence(str): What state the device presence should be set to.
-            default is "online".
-        filter(filter_id): A filter to apply to the events returned.
-
-    Response JSON::
-        {
-          "next_batch": // batch token for the next /sync
-          "presence": // presence data for the user.
-          "rooms": {
-            "join": { // Joined rooms being updated.
-              "${room_id}": { // Id of the room being updated
-                "event_map": // Map of EventID -> event JSON.
-                "timeline": { // The recent events in the room if gap is "true"
-                  "limited": // Was the per-room event limit exceeded?
-                             // otherwise the next events in the room.
-                  "events": [] // list of EventIDs in the "event_map".
-                  "prev_batch": // back token for getting previous events.
-                }
-                "state": {"events": []} // list of EventIDs updating the
-                                        // current state to be what it should
-                                        // be at the end of the batch.
-                "ephemeral": {"events": []} // list of event objects
-              }
-            },
-            "invite": {}, // Invited rooms being updated.
-            "leave": {} // Archived rooms being updated.
-          }
-        }
-    """
-
-    PATTERNS = client_patterns("/sync$")
-    ALLOWED_PRESENCE = {"online", "offline", "unavailable"}
-
-    def __init__(self, hs: "HomeServer"):
-        super().__init__()
-        self.hs = hs
-        self.auth = hs.get_auth()
-        self.store = hs.get_datastore()
-        self.sync_handler = hs.get_sync_handler()
-        self.clock = hs.get_clock()
-        self.filtering = hs.get_filtering()
-        self.presence_handler = hs.get_presence_handler()
-        self._server_notices_sender = hs.get_server_notices_sender()
-        self._event_serializer = hs.get_event_client_serializer()
-
-    async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
-        # This will always be set by the time Twisted calls us.
-        assert request.args is not None
-
-        if b"from" in request.args:
-            # /events used to use 'from', but /sync uses 'since'.
-            # Lets be helpful and whine if we see a 'from'.
-            raise SynapseError(
-                400, "'from' is not a valid query parameter. Did you mean 'since'?"
-            )
-
-        requester = await self.auth.get_user_by_req(request, allow_guest=True)
-        user = requester.user
-        device_id = requester.device_id
-
-        timeout = parse_integer(request, "timeout", default=0)
-        since = parse_string(request, "since")
-        set_presence = parse_string(
-            request,
-            "set_presence",
-            default="online",
-            allowed_values=self.ALLOWED_PRESENCE,
-        )
-        filter_id = parse_string(request, "filter")
-        full_state = parse_boolean(request, "full_state", default=False)
-
-        logger.debug(
-            "/sync: user=%r, timeout=%r, since=%r, "
-            "set_presence=%r, filter_id=%r, device_id=%r",
-            user,
-            timeout,
-            since,
-            set_presence,
-            filter_id,
-            device_id,
-        )
-
-        request_key = (user, timeout, since, filter_id, full_state, device_id)
-
-        if filter_id is None:
-            filter_collection = DEFAULT_FILTER_COLLECTION
-        elif filter_id.startswith("{"):
-            try:
-                filter_object = json_decoder.decode(filter_id)
-                set_timeline_upper_limit(
-                    filter_object, self.hs.config.filter_timeline_limit
-                )
-            except Exception:
-                raise SynapseError(400, "Invalid filter JSON")
-            self.filtering.check_valid_filter(filter_object)
-            filter_collection = FilterCollection(filter_object)
-        else:
-            try:
-                filter_collection = await self.filtering.get_user_filter(
-                    user.localpart, filter_id
-                )
-            except StoreError as err:
-                if err.code != 404:
-                    raise
-                # fix up the description and errcode to be more useful
-                raise SynapseError(400, "No such filter", errcode=Codes.INVALID_PARAM)
-
-        sync_config = SyncConfig(
-            user=user,
-            filter_collection=filter_collection,
-            is_guest=requester.is_guest,
-            request_key=request_key,
-            device_id=device_id,
-        )
-
-        since_token = None
-        if since is not None:
-            since_token = await StreamToken.from_string(self.store, since)
-
-        # send any outstanding server notices to the user.
-        await self._server_notices_sender.on_user_syncing(user.to_string())
-
-        affect_presence = set_presence != PresenceState.OFFLINE
-
-        if affect_presence:
-            await self.presence_handler.set_state(
-                user, {"presence": set_presence}, True
-            )
-
-        context = await self.presence_handler.user_syncing(
-            user.to_string(), affect_presence=affect_presence
-        )
-        with context:
-            sync_result = await self.sync_handler.wait_for_sync_for_user(
-                requester,
-                sync_config,
-                since_token=since_token,
-                timeout=timeout,
-                full_state=full_state,
-            )
-
-        # the client may have disconnected by now; don't bother to serialize the
-        # response if so.
-        if request._disconnected:
-            logger.info("Client has disconnected; not serializing response.")
-            return 200, {}
-
-        time_now = self.clock.time_msec()
-        response_content = await self.encode_response(
-            time_now, sync_result, requester.access_token_id, filter_collection
-        )
-
-        logger.debug("Event formatting complete")
-        return 200, response_content
-
-    async def encode_response(self, time_now, sync_result, access_token_id, filter):
-        logger.debug("Formatting events in sync response")
-        if filter.event_format == "client":
-            event_formatter = format_event_for_client_v2_without_room_id
-        elif filter.event_format == "federation":
-            event_formatter = format_event_raw
-        else:
-            raise Exception("Unknown event format %s" % (filter.event_format,))
-
-        joined = await self.encode_joined(
-            sync_result.joined,
-            time_now,
-            access_token_id,
-            filter.event_fields,
-            event_formatter,
-        )
-
-        invited = await self.encode_invited(
-            sync_result.invited, time_now, access_token_id, event_formatter
-        )
-
-        knocked = await self.encode_knocked(
-            sync_result.knocked, time_now, access_token_id, event_formatter
-        )
-
-        archived = await self.encode_archived(
-            sync_result.archived,
-            time_now,
-            access_token_id,
-            filter.event_fields,
-            event_formatter,
-        )
-
-        logger.debug("building sync response dict")
-
-        response: dict = defaultdict(dict)
-        response["next_batch"] = await sync_result.next_batch.to_string(self.store)
-
-        if sync_result.account_data:
-            response["account_data"] = {"events": sync_result.account_data}
-        if sync_result.presence:
-            response["presence"] = SyncRestServlet.encode_presence(
-                sync_result.presence, time_now
-            )
-
-        if sync_result.to_device:
-            response["to_device"] = {"events": sync_result.to_device}
-
-        if sync_result.device_lists.changed:
-            response["device_lists"]["changed"] = list(sync_result.device_lists.changed)
-        if sync_result.device_lists.left:
-            response["device_lists"]["left"] = list(sync_result.device_lists.left)
-
-        # We always include this because https://github.com/vector-im/element-android/issues/3725
-        # The spec isn't terribly clear on when this can be omitted and how a client would tell
-        # the difference between "no keys present" and "nothing changed" in terms of whole field
-        # absent / individual key type entry absent
-        # Corresponding synapse issue: https://github.com/matrix-org/synapse/issues/10456
-        response["device_one_time_keys_count"] = sync_result.device_one_time_keys_count
-
-        # https://github.com/matrix-org/matrix-doc/blob/54255851f642f84a4f1aaf7bc063eebe3d76752b/proposals/2732-olm-fallback-keys.md
-        # states that this field should always be included, as long as the server supports the feature.
-        response[
-            "org.matrix.msc2732.device_unused_fallback_key_types"
-        ] = sync_result.device_unused_fallback_key_types
-
-        if joined:
-            response["rooms"][Membership.JOIN] = joined
-        if invited:
-            response["rooms"][Membership.INVITE] = invited
-        if knocked:
-            response["rooms"][Membership.KNOCK] = knocked
-        if archived:
-            response["rooms"][Membership.LEAVE] = archived
-
-        if sync_result.groups.join:
-            response["groups"][Membership.JOIN] = sync_result.groups.join
-        if sync_result.groups.invite:
-            response["groups"][Membership.INVITE] = sync_result.groups.invite
-        if sync_result.groups.leave:
-            response["groups"][Membership.LEAVE] = sync_result.groups.leave
-
-        return response
-
-    @staticmethod
-    def encode_presence(events, time_now):
-        return {
-            "events": [
-                {
-                    "type": "m.presence",
-                    "sender": event.user_id,
-                    "content": format_user_presence_state(
-                        event, time_now, include_user_id=False
-                    ),
-                }
-                for event in events
-            ]
-        }
-
-    async def encode_joined(
-        self, rooms, time_now, token_id, event_fields, event_formatter
-    ):
-        """
-        Encode the joined rooms in a sync result
-
-        Args:
-            rooms(list[synapse.handlers.sync.JoinedSyncResult]): list of sync
-                results for rooms this user is joined to
-            time_now(int): current time - used as a baseline for age
-                calculations
-            token_id(int): ID of the user's auth token - used for namespacing
-                of transaction IDs
-            event_fields(list<str>): List of event fields to include. If empty,
-                all fields will be returned.
-            event_formatter (func[dict]): function to convert from federation format
-                to client format
-        Returns:
-            dict[str, dict[str, object]]: the joined rooms list, in our
-                response format
-        """
-        joined = {}
-        for room in rooms:
-            joined[room.room_id] = await self.encode_room(
-                room,
-                time_now,
-                token_id,
-                joined=True,
-                only_fields=event_fields,
-                event_formatter=event_formatter,
-            )
-
-        return joined
-
-    async def encode_invited(self, rooms, time_now, token_id, event_formatter):
-        """
-        Encode the invited rooms in a sync result
-
-        Args:
-            rooms(list[synapse.handlers.sync.InvitedSyncResult]): list of
-                sync results for rooms this user is invited to
-            time_now(int): current time - used as a baseline for age
-                calculations
-            token_id(int): ID of the user's auth token - used for namespacing
-                of transaction IDs
-            event_formatter (func[dict]): function to convert from federation format
-                to client format
-
-        Returns:
-            dict[str, dict[str, object]]: the invited rooms list, in our
-                response format
-        """
-        invited = {}
-        for room in rooms:
-            invite = await self._event_serializer.serialize_event(
-                room.invite,
-                time_now,
-                token_id=token_id,
-                event_format=event_formatter,
-                include_stripped_room_state=True,
-            )
-            unsigned = dict(invite.get("unsigned", {}))
-            invite["unsigned"] = unsigned
-            invited_state = list(unsigned.pop("invite_room_state", []))
-            invited_state.append(invite)
-            invited[room.room_id] = {"invite_state": {"events": invited_state}}
-
-        return invited
-
-    async def encode_knocked(
-        self,
-        rooms: List[KnockedSyncResult],
-        time_now: int,
-        token_id: int,
-        event_formatter: Callable[[Dict], Dict],
-    ) -> Dict[str, Dict[str, Any]]:
-        """
-        Encode the rooms we've knocked on in a sync result.
-
-        Args:
-            rooms: list of sync results for rooms this user is knocking on
-            time_now: current time - used as a baseline for age calculations
-            token_id: ID of the user's auth token - used for namespacing of transaction IDs
-            event_formatter: function to convert from federation format to client format
-
-        Returns:
-            The list of rooms the user has knocked on, in our response format.
-        """
-        knocked = {}
-        for room in rooms:
-            knock = await self._event_serializer.serialize_event(
-                room.knock,
-                time_now,
-                token_id=token_id,
-                event_format=event_formatter,
-                include_stripped_room_state=True,
-            )
-
-            # Extract the `unsigned` key from the knock event.
-            # This is where we (cheekily) store the knock state events
-            unsigned = knock.setdefault("unsigned", {})
-
-            # Duplicate the dictionary in order to avoid modifying the original
-            unsigned = dict(unsigned)
-
-            # Extract the stripped room state from the unsigned dict
-            # This is for clients to get a little bit of information about
-            # the room they've knocked on, without revealing any sensitive information
-            knocked_state = list(unsigned.pop("knock_room_state", []))
-
-            # Append the actual knock membership event itself as well. This provides
-            # the client with:
-            #
-            # * A knock state event that they can use for easier internal tracking
-            # * The rough timestamp of when the knock occurred contained within the event
-            knocked_state.append(knock)
-
-            # Build the `knock_state` dictionary, which will contain the state of the
-            # room that the client has knocked on
-            knocked[room.room_id] = {"knock_state": {"events": knocked_state}}
-
-        return knocked
-
-    async def encode_archived(
-        self, rooms, time_now, token_id, event_fields, event_formatter
-    ):
-        """
-        Encode the archived rooms in a sync result
-
-        Args:
-            rooms (list[synapse.handlers.sync.ArchivedSyncResult]): list of
-                sync results for rooms this user is joined to
-            time_now(int): current time - used as a baseline for age
-                calculations
-            token_id(int): ID of the user's auth token - used for namespacing
-                of transaction IDs
-            event_fields(list<str>): List of event fields to include. If empty,
-                all fields will be returned.
-            event_formatter (func[dict]): function to convert from federation format
-                to client format
-        Returns:
-            dict[str, dict[str, object]]: The invited rooms list, in our
-                response format
-        """
-        joined = {}
-        for room in rooms:
-            joined[room.room_id] = await self.encode_room(
-                room,
-                time_now,
-                token_id,
-                joined=False,
-                only_fields=event_fields,
-                event_formatter=event_formatter,
-            )
-
-        return joined
-
-    async def encode_room(
-        self, room, time_now, token_id, joined, only_fields, event_formatter
-    ):
-        """
-        Args:
-            room (JoinedSyncResult|ArchivedSyncResult): sync result for a
-                single room
-            time_now (int): current time - used as a baseline for age
-                calculations
-            token_id (int): ID of the user's auth token - used for namespacing
-                of transaction IDs
-            joined (bool): True if the user is joined to this room - will mean
-                we handle ephemeral events
-            only_fields(list<str>): Optional. The list of event fields to include.
-            event_formatter (func[dict]): function to convert from federation format
-                to client format
-        Returns:
-            dict[str, object]: the room, encoded in our response format
-        """
-
-        def serialize(events):
-            return self._event_serializer.serialize_events(
-                events,
-                time_now=time_now,
-                # We don't bundle "live" events, as otherwise clients
-                # will end up double counting annotations.
-                bundle_aggregations=False,
-                token_id=token_id,
-                event_format=event_formatter,
-                only_event_fields=only_fields,
-            )
-
-        state_dict = room.state
-        timeline_events = room.timeline.events
-
-        state_events = state_dict.values()
-
-        for event in itertools.chain(state_events, timeline_events):
-            # We've had bug reports that events were coming down under the
-            # wrong room.
-            if event.room_id != room.room_id:
-                logger.warning(
-                    "Event %r is under room %r instead of %r",
-                    event.event_id,
-                    room.room_id,
-                    event.room_id,
-                )
-
-        serialized_state = await serialize(state_events)
-        serialized_timeline = await serialize(timeline_events)
-
-        account_data = room.account_data
-
-        result = {
-            "timeline": {
-                "events": serialized_timeline,
-                "prev_batch": await room.timeline.prev_batch.to_string(self.store),
-                "limited": room.timeline.limited,
-            },
-            "state": {"events": serialized_state},
-            "account_data": {"events": account_data},
-        }
-
-        if joined:
-            ephemeral_events = room.ephemeral
-            result["ephemeral"] = {"events": ephemeral_events}
-            result["unread_notifications"] = room.unread_notifications
-            result["summary"] = room.summary
-            result["org.matrix.msc2654.unread_count"] = room.unread_count
-
-        return result
-
-
-def register_servlets(hs, http_server):
-    SyncRestServlet(hs).register(http_server)
diff --git a/synapse/rest/client/v2_alpha/tags.py b/synapse/rest/client/v2_alpha/tags.py
deleted file mode 100644
index c14f83be18..0000000000
--- a/synapse/rest/client/v2_alpha/tags.py
+++ /dev/null
@@ -1,85 +0,0 @@
-# Copyright 2015, 2016 OpenMarket Ltd
-#
-# 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.
-
-import logging
-
-from synapse.api.errors import AuthError
-from synapse.http.servlet import RestServlet, parse_json_object_from_request
-
-from ._base import client_patterns
-
-logger = logging.getLogger(__name__)
-
-
-class TagListServlet(RestServlet):
-    """
-    GET /user/{user_id}/rooms/{room_id}/tags HTTP/1.1
-    """
-
-    PATTERNS = client_patterns("/user/(?P<user_id>[^/]*)/rooms/(?P<room_id>[^/]*)/tags")
-
-    def __init__(self, hs):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.store = hs.get_datastore()
-
-    async def on_GET(self, request, user_id, room_id):
-        requester = await self.auth.get_user_by_req(request)
-        if user_id != requester.user.to_string():
-            raise AuthError(403, "Cannot get tags for other users.")
-
-        tags = await self.store.get_tags_for_room(user_id, room_id)
-
-        return 200, {"tags": tags}
-
-
-class TagServlet(RestServlet):
-    """
-    PUT /user/{user_id}/rooms/{room_id}/tags/{tag} HTTP/1.1
-    DELETE /user/{user_id}/rooms/{room_id}/tags/{tag} HTTP/1.1
-    """
-
-    PATTERNS = client_patterns(
-        "/user/(?P<user_id>[^/]*)/rooms/(?P<room_id>[^/]*)/tags/(?P<tag>[^/]*)"
-    )
-
-    def __init__(self, hs):
-        super().__init__()
-        self.auth = hs.get_auth()
-        self.handler = hs.get_account_data_handler()
-
-    async def on_PUT(self, request, user_id, room_id, tag):
-        requester = await self.auth.get_user_by_req(request)
-        if user_id != requester.user.to_string():
-            raise AuthError(403, "Cannot add tags for other users.")
-
-        body = parse_json_object_from_request(request)
-
-        await self.handler.add_tag_to_room(user_id, room_id, tag, body)
-
-        return 200, {}
-
-    async def on_DELETE(self, request, user_id, room_id, tag):
-        requester = await self.auth.get_user_by_req(request)
-        if user_id != requester.user.to_string():
-            raise AuthError(403, "Cannot add tags for other users.")
-
-        await self.handler.remove_tag_from_room(user_id, room_id, tag)
-
-        return 200, {}
-
-
-def register_servlets(hs, http_server):
-    TagListServlet(hs).register(http_server)
-    TagServlet(hs).register(http_server)
diff --git a/synapse/rest/client/v2_alpha/thirdparty.py b/synapse/rest/client/v2_alpha/thirdparty.py
deleted file mode 100644
index b5c67c9bb6..0000000000
--- a/synapse/rest/client/v2_alpha/thirdparty.py
+++ /dev/null
@@ -1,111 +0,0 @@
-# Copyright 2015, 2016 OpenMarket Ltd
-#
-# 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.
-
-
-import logging
-
-from synapse.api.constants import ThirdPartyEntityKind
-from synapse.http.servlet import RestServlet
-
-from ._base import client_patterns
-
-logger = logging.getLogger(__name__)
-
-
-class ThirdPartyProtocolsServlet(RestServlet):
-    PATTERNS = client_patterns("/thirdparty/protocols")
-
-    def __init__(self, hs):
-        super().__init__()
-
-        self.auth = hs.get_auth()
-        self.appservice_handler = hs.get_application_service_handler()
-
-    async def on_GET(self, request):
-        await self.auth.get_user_by_req(request, allow_guest=True)
-
-        protocols = await self.appservice_handler.get_3pe_protocols()
-        return 200, protocols
-
-
-class ThirdPartyProtocolServlet(RestServlet):
-    PATTERNS = client_patterns("/thirdparty/protocol/(?P<protocol>[^/]+)$")
-
-    def __init__(self, hs):
-        super().__init__()
-
-        self.auth = hs.get_auth()
-        self.appservice_handler = hs.get_application_service_handler()
-
-    async def on_GET(self, request, protocol):
-        await self.auth.get_user_by_req(request, allow_guest=True)
-
-        protocols = await self.appservice_handler.get_3pe_protocols(
-            only_protocol=protocol
-        )
-        if protocol in protocols:
-            return 200, protocols[protocol]
-        else:
-            return 404, {"error": "Unknown protocol"}
-
-
-class ThirdPartyUserServlet(RestServlet):
-    PATTERNS = client_patterns("/thirdparty/user(/(?P<protocol>[^/]+))?$")
-
-    def __init__(self, hs):
-        super().__init__()
-
-        self.auth = hs.get_auth()
-        self.appservice_handler = hs.get_application_service_handler()
-
-    async def on_GET(self, request, protocol):
-        await self.auth.get_user_by_req(request, allow_guest=True)
-
-        fields = request.args
-        fields.pop(b"access_token", None)
-
-        results = await self.appservice_handler.query_3pe(
-            ThirdPartyEntityKind.USER, protocol, fields
-        )
-
-        return 200, results
-
-
-class ThirdPartyLocationServlet(RestServlet):
-    PATTERNS = client_patterns("/thirdparty/location(/(?P<protocol>[^/]+))?$")
-
-    def __init__(self, hs):
-        super().__init__()
-
-        self.auth = hs.get_auth()
-        self.appservice_handler = hs.get_application_service_handler()
-
-    async def on_GET(self, request, protocol):
-        await self.auth.get_user_by_req(request, allow_guest=True)
-
-        fields = request.args
-        fields.pop(b"access_token", None)
-
-        results = await self.appservice_handler.query_3pe(
-            ThirdPartyEntityKind.LOCATION, protocol, fields
-        )
-
-        return 200, results
-
-
-def register_servlets(hs, http_server):
-    ThirdPartyProtocolsServlet(hs).register(http_server)
-    ThirdPartyProtocolServlet(hs).register(http_server)
-    ThirdPartyUserServlet(hs).register(http_server)
-    ThirdPartyLocationServlet(hs).register(http_server)
diff --git a/synapse/rest/client/v2_alpha/tokenrefresh.py b/synapse/rest/client/v2_alpha/tokenrefresh.py
deleted file mode 100644
index b2f858545c..0000000000
--- a/synapse/rest/client/v2_alpha/tokenrefresh.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# Copyright 2015, 2016 OpenMarket Ltd
-#
-# 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.
-
-from synapse.api.errors import AuthError
-from synapse.http.servlet import RestServlet
-
-from ._base import client_patterns
-
-
-class TokenRefreshRestServlet(RestServlet):
-    """
-    Exchanges refresh tokens for a pair of an access token and a new refresh
-    token.
-    """
-
-    PATTERNS = client_patterns("/tokenrefresh")
-
-    def __init__(self, hs):
-        super().__init__()
-
-    async def on_POST(self, request):
-        raise AuthError(403, "tokenrefresh is no longer supported.")
-
-
-def register_servlets(hs, http_server):
-    TokenRefreshRestServlet(hs).register(http_server)
diff --git a/synapse/rest/client/v2_alpha/user_directory.py b/synapse/rest/client/v2_alpha/user_directory.py
deleted file mode 100644
index 7e8912f0b9..0000000000
--- a/synapse/rest/client/v2_alpha/user_directory.py
+++ /dev/null
@@ -1,79 +0,0 @@
-# Copyright 2017 Vector Creations Ltd
-#
-# 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.
-
-import logging
-
-from synapse.api.errors import SynapseError
-from synapse.http.servlet import RestServlet, parse_json_object_from_request
-
-from ._base import client_patterns
-
-logger = logging.getLogger(__name__)
-
-
-class UserDirectorySearchRestServlet(RestServlet):
-    PATTERNS = client_patterns("/user_directory/search$")
-
-    def __init__(self, hs):
-        """
-        Args:
-            hs (synapse.server.HomeServer): server
-        """
-        super().__init__()
-        self.hs = hs
-        self.auth = hs.get_auth()
-        self.user_directory_handler = hs.get_user_directory_handler()
-
-    async def on_POST(self, request):
-        """Searches for users in directory
-
-        Returns:
-            dict of the form::
-
-                {
-                    "limited": <bool>,  # whether there were more results or not
-                    "results": [  # Ordered by best match first
-                        {
-                            "user_id": <user_id>,
-                            "display_name": <display_name>,
-                            "avatar_url": <avatar_url>
-                        }
-                    ]
-                }
-        """
-        requester = await self.auth.get_user_by_req(request, allow_guest=False)
-        user_id = requester.user.to_string()
-
-        if not self.hs.config.user_directory_search_enabled:
-            return 200, {"limited": False, "results": []}
-
-        body = parse_json_object_from_request(request)
-
-        limit = body.get("limit", 10)
-        limit = min(limit, 50)
-
-        try:
-            search_term = body["search_term"]
-        except Exception:
-            raise SynapseError(400, "`search_term` is required field")
-
-        results = await self.user_directory_handler.search_users(
-            user_id, search_term, limit
-        )
-
-        return 200, results
-
-
-def register_servlets(hs, http_server):
-    UserDirectorySearchRestServlet(hs).register(http_server)