Stabilize the M_APPSERVICE_LOGIN_UNSUPPORTED error code (#20180)

Part of: https://github.com/element-hq/synapse/issues/19415

Return `M_APPSERVICE_LOGIN_UNSUPPORTED` error code instead of the
unstable `IO.ELEMENT.MSC4190.M_APPSERVICE_LOGIN_UNSUPPORTED` identifier.

> Servers MUST still allow application services to use the `/register`
endpoint with a login type of `m.login.application_service` even if they
don't support the Legacy Authentication API. In that case application
services MUST set the `"inhibit_login": true` parameter as they cannot
use it to log in as users. If the `inhibit_login` parameter is not set
to `true`, the server MUST return a 400 HTTP status code with an
`M_APPSERVICE_LOGIN_UNSUPPORTED` error code.
>
> [...]
>
> Application services MUST NOT use the `/login` endpoint if the server
doesn't support the Legacy authentication API. If `/login` is called
with the `m.login.application_service` login type the server MUST return
a 400 HTTP status code with an `M_APPSERVICE_LOGIN_UNSUPPORTED` error
code.
>
> — [Matrix v1.19, Application Service
API](https://spec.matrix.org/v1.19/application-service-api/#registration)

Synapse returns the correct 400 on both endpoints, but with the unstable
identifier.

Before:

```
POST /_matrix/client/v3/login    {"type": "m.login.application_service", ...}  # appservice with MSC4190 device management
POST /_matrix/client/v3/register {"type": "m.login.application_service", ...}  # without "inhibit_login": true
  400 {"errcode": "IO.ELEMENT.MSC4190.M_APPSERVICE_LOGIN_UNSUPPORTED"}
```

After:

```
POST /_matrix/client/v3/login    {"type": "m.login.application_service", ...}  # appservice with MSC4190 device management
POST /_matrix/client/v3/register {"type": "m.login.application_service", ...}  # without "inhibit_login": true
  400 {"errcode": "M_APPSERVICE_LOGIN_UNSUPPORTED"}
```

A Sister PR exists in MAS:
https://github.com/element-hq/matrix-authentication-service/pull/5961 ;
when delegation is enabled, `/login` reaches MAS instead of Synapse, and
MAS currently answers `m.login.application_service` with `M_UNKNOWN`.
This commit is contained in:
Paul Chobert
2026-09-03 15:07:51 +01:00
committed by GitHub
parent 366a020f79
commit 207f6560ce
5 changed files with 33 additions and 4 deletions
+1
View File
@@ -0,0 +1 @@
Return the stable `M_APPSERVICE_LOGIN_UNSUPPORTED` error code, added in Matrix 1.17, instead of its unstable MSC4190-prefixed identifier.
+1 -1
View File
@@ -143,7 +143,7 @@ class Codes(str, Enum):
INVITE_BLOCKED = "M_INVITE_BLOCKED"
# Part of MSC4190
APPSERVICE_LOGIN_UNSUPPORTED = "IO.ELEMENT.MSC4190.M_APPSERVICE_LOGIN_UNSUPPORTED"
APPSERVICE_LOGIN_UNSUPPORTED = "M_APPSERVICE_LOGIN_UNSUPPORTED"
# Part of MSC4306: Thread Subscriptions
MSC4306_CONFLICTING_UNSUBSCRIPTION = (
+28
View File
@@ -587,6 +587,34 @@ class DisabledEndpointsTestCase(HomeserverTestCase):
"POST", "/_matrix/client/v3/register/msisdn/requestToken"
)
def test_appservice_registration_without_inhibit_login(self) -> None:
"""Test that appservice registration without `inhibit_login` is rejected
with the `M_APPSERVICE_LOGIN_UNSUPPORTED` errcode."""
appservice = ApplicationService(
token="i_am_an_app_service",
id="1234",
namespaces={"users": [{"regex": r"@alice:.+", "exclusive": True}]},
sender=UserID.from_string("@as_main:test"),
)
self.hs.get_datastores().main.services_cache = [appservice]
channel = self.make_request(
"POST",
"/_matrix/client/v3/register",
{
"username": "alice",
"type": "m.login.application_service",
},
shorthand=False,
access_token="i_am_an_app_service",
)
self.assertEqual(channel.code, 400, channel.json_body)
self.assertEqual(
channel.json_body.get("errcode"),
"M_APPSERVICE_LOGIN_UNSUPPORTED",
channel.json_body,
)
def test_session_management_endpoints_removed(self) -> None:
"""Test that session management endpoints that were removed in MSC2964 are no longer available."""
self.expect_unrecognized("GET", "/_matrix/client/v3/login")
+2 -2
View File
@@ -1521,7 +1521,7 @@ class AppserviceLoginRestServletTestCase(unittest.HomeserverTestCase):
self.assertEqual(channel.code, 200, msg=channel.result)
def test_login_appservice_msc4190_fail(self) -> None:
"""Test that an appservice user can use /login"""
"""Test that an appservice with MSC4190 enabled can't use /login"""
self.register_appservice_user(
"as3_user_alice", self.msc4190_service.token, inhibit_login=True
)
@@ -1537,7 +1537,7 @@ class AppserviceLoginRestServletTestCase(unittest.HomeserverTestCase):
self.assertEqual(channel.code, 400, msg=channel.result)
self.assertEqual(
channel.json_body.get("errcode"),
Codes.APPSERVICE_LOGIN_UNSUPPORTED,
"M_APPSERVICE_LOGIN_UNSUPPORTED",
channel.json_body,
)
+1 -1
View File
@@ -183,7 +183,7 @@ class RegisterRestServletTestCase(unittest.HomeserverTestCase):
self.assertEqual(channel.code, 400, channel.json_body)
self.assertEqual(
channel.json_body.get("errcode"),
Codes.APPSERVICE_LOGIN_UNSUPPORTED,
"M_APPSERVICE_LOGIN_UNSUPPORTED",
channel.json_body,
)