diff --git a/changelog.d/17509.feature b/changelog.d/17509.feature
new file mode 100644
index 0000000000..6d639ceb98
--- /dev/null
+++ b/changelog.d/17509.feature
@@ -0,0 +1 @@
+Improve cross-signing upload when using [MSC3861](https://github.com/matrix-org/matrix-spec-proposals/pull/3861) to use a custom UIA flow stage, with web fallback support.
diff --git a/synapse/rest/client/auth.py b/synapse/rest/client/auth.py
index 4221f35937..32eeecd662 100644
--- a/synapse/rest/client/auth.py
+++ b/synapse/rest/client/auth.py
@@ -27,7 +27,7 @@ from twisted.web.server import Request
from synapse.api.constants import LoginType
from synapse.api.errors import LoginError, SynapseError
from synapse.api.urls import CLIENT_API_PREFIX
-from synapse.http.server import HttpServer, respond_with_html
+from synapse.http.server import HttpServer, respond_with_html, respond_with_redirect
from synapse.http.servlet import RestServlet, parse_string
from synapse.http.site import SynapseRequest
@@ -66,6 +66,17 @@ class AuthRestServlet(RestServlet):
if not session:
raise SynapseError(400, "No session supplied")
+ if (
+ self.hs.config.experimental.msc3861.enabled
+ and stagetype == "org.matrix.cross_signing_reset"
+ ):
+ config = self.hs.config.experimental.msc3861
+ if config.account_management_url is not None:
+ url = f"{config.account_management_url}?action=org.matrix.cross_signing_reset"
+ else:
+ url = config.issuer
+ respond_with_redirect(request, str.encode(url))
+
if stagetype == LoginType.RECAPTCHA:
html = self.recaptcha_template.render(
session=session,
diff --git a/synapse/rest/client/keys.py b/synapse/rest/client/keys.py
index eddad7d5b8..a33eb6c1f2 100644
--- a/synapse/rest/client/keys.py
+++ b/synapse/rest/client/keys.py
@@ -23,10 +23,13 @@
import logging
import re
from collections import Counter
-from http import HTTPStatus
from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple
-from synapse.api.errors import Codes, InvalidAPICallError, SynapseError
+from synapse.api.errors import (
+ InteractiveAuthIncompleteError,
+ InvalidAPICallError,
+ SynapseError,
+)
from synapse.http.server import HttpServer
from synapse.http.servlet import (
RestServlet,
@@ -409,11 +412,24 @@ class SigningKeyUploadServlet(RestServlet):
else:
url = config.issuer
- raise SynapseError(
- HTTPStatus.NOT_IMPLEMENTED,
- "To reset your end-to-end encryption cross-signing identity, "
- f"you first need to approve it at {url} and then try again.",
- Codes.UNRECOGNIZED,
+ # We use a dummy session ID as this isn't really a UIA flow, but we
+ # reuse the same API shape for better client compatibility.
+ raise InteractiveAuthIncompleteError(
+ "dummy",
+ {
+ "session": "dummy",
+ "flows": [
+ {"stages": ["org.matrix.cross_signing_reset"]},
+ ],
+ "params": {
+ "org.matrix.cross_signing_reset": {
+ "url": url,
+ },
+ },
+ "msg": "To reset your end-to-end encryption cross-signing "
+ f"identity, you first need to approve it at {url} and "
+ "then try again.",
+ },
)
else:
# Without MSC3861, we require UIA.
diff --git a/tests/handlers/test_oauth_delegation.py b/tests/handlers/test_oauth_delegation.py
index 036c539db2..5b5dc713d1 100644
--- a/tests/handlers/test_oauth_delegation.py
+++ b/tests/handlers/test_oauth_delegation.py
@@ -550,7 +550,7 @@ class MSC3861OAuthDelegation(HomeserverTestCase):
access_token="mockAccessToken",
)
- self.assertEqual(channel.code, HTTPStatus.NOT_IMPLEMENTED, channel.json_body)
+ self.assertEqual(channel.code, HTTPStatus.UNAUTHORIZED, channel.json_body)
def expect_unauthorized(
self, method: str, path: str, content: Union[bytes, str, JsonDict] = ""
diff --git a/tests/rest/client/test_keys.py b/tests/rest/client/test_keys.py
index 8bbd109092..d9a210b616 100644
--- a/tests/rest/client/test_keys.py
+++ b/tests/rest/client/test_keys.py
@@ -315,9 +315,7 @@ class SigningKeyUploadServletTestCase(unittest.HomeserverTestCase):
"master_key": master_key2,
},
)
- self.assertEqual(
- channel.code, HTTPStatus.NOT_IMPLEMENTED, channel.json_body
- )
+ self.assertEqual(channel.code, HTTPStatus.UNAUTHORIZED, channel.json_body)
# Pretend that MAS did UIA and allowed us to replace the master key.
channel = self.make_request(
@@ -349,9 +347,7 @@ class SigningKeyUploadServletTestCase(unittest.HomeserverTestCase):
"master_key": master_key3,
},
)
- self.assertEqual(
- channel.code, HTTPStatus.NOT_IMPLEMENTED, channel.json_body
- )
+ self.assertEqual(channel.code, HTTPStatus.UNAUTHORIZED, channel.json_body)
# Pretend that MAS did UIA and allowed us to replace the master key.
channel = self.make_request(
@@ -376,6 +372,4 @@ class SigningKeyUploadServletTestCase(unittest.HomeserverTestCase):
"master_key": master_key3,
},
)
- self.assertEqual(
- channel.code, HTTPStatus.NOT_IMPLEMENTED, channel.json_body
- )
+ self.assertEqual(channel.code, HTTPStatus.UNAUTHORIZED, channel.json_body)
|