diff --git a/README.md b/README.md index dbcdf181f..47435d6a7 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ same major points is fine. Some tips for MSC writing: -* Please wrap your lines to 80 characters maximum (some small leeway is OK). +* Please wrap your lines to 120 characters maximum. This allows readers to review your markdown without needing to horizontally scroll back and forth. Many markdown text editors have this a feature. * If you are referencing an existing endpoint in the spec, or another MSC, it diff --git a/proposals/2246-asynchronous-uploads.md b/proposals/2246-asynchronous-uploads.md new file mode 100644 index 000000000..3345b998c --- /dev/null +++ b/proposals/2246-asynchronous-uploads.md @@ -0,0 +1,152 @@ +# MSC2246: Asynchronous media uploads +Sending media to Matrix currently requires that clients first upload the media +to the content repository and then send the event. This is a problem for some +use cases, such as bridges that want to preserve message order, as reuploading +a large file would block all messages. + +## Proposal +This proposal proposes a way to send the event containing media before actually +uploading the media, which would make the aforementioned bridge message order +preservation possible without blocking all other messages behind a long upload. + +In the future, this new functionality could be used for streaming file +transfers, as requested in [matrix-spec#432]. + +### Content repository behavior +The proposal adds two new endpoints to the content repository API and modifies +the download and thumbnail endpoints. + +#### `POST /_matrix/media/v1/create` +Create a new MXC URI without content. Like `/upload`, this endpoint requires +auth, can be rate limited, and returns the `content_uri` that can be used in +events. + +The request body should be an empty JSON object. In the future, the body could +be used for metadata about the file, such as the mime type or access control +settings (related: [MSC701]). + +The server may optionally enforce a maximum age for unused media IDs to delete +media IDs when the client doesn't start the upload in time, or when the upload +was interrupted and not resumed in time. The server should include the maximum +POSIX millisecond timestamp to complete the upload in the `unused_expires_at` +field in the response JSON. The recommended default expiration is 24 hours which +should be enough time to accommodate users on poor connection who find a better +connection to complete the upload. + +##### Rate Limiting + +The server should rate limit requests to create media. + +The server should limit the number of concurrent *pending media uploads* a given +user can have. A pending media upload is a created MXC URI that (a) is not +expired (the `unused_expires_at` timestamp has not passed) and (b) the media has +not yet been uploaded for. + +In both cases, the server should respond with `M_LIMIT_EXCEEDED` optionally +providing details in the `error` field, but servers may wish to obscure the +exact limits that are used and not provide such details. + +##### Example response +```json +{ + "content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw", + "unused_expires_at": 1647257217083 +} +``` + +#### `PUT /_matrix/media/v3/upload/{serverName}/{mediaId}` +Upload content to a MXC URI that was created earlier. This endpoint requires +auth. If the upload is successful, an empty JSON object and status code 200 is +returned. Rate limiting additionally can apply here. + +If the endpoint is called with a media ID that already has content, the request +should be rejected with the error code `M_CANNOT_OVERWRITE_MEDIA` and HTTP +status code 409. + +If the upload request comes from a user other than the one who created the media +ID, the request should be rejected with an `M_FORBIDDEN` error. + +If the serverName/mediaId combination is not known, not local, or expired, an +`M_NOT_FOUND` error is returned. + +If the MXC's `unused_expires_at` is reached before the upload completes, the +server may either respond immediately with `M_NOT_FOUND` or allow the upload to +continue. + +For other errors, such as file size, file type or user quota errors, the normal +`/upload` rules apply. + +#### Changes to the `/download` and `/thumbnail` endpoints +A new query parameter, `timeout_ms` is added to the endpoints that can +download media. It's an integer that specifies the maximum number of +milliseconds that the client is willing to wait to start receiving data. +The default value is 20000 (20 seconds). The content repository can and should +impose a maximum value for this parameter. The content repository can also +choose to respond before the timeout if it desires. + +If the media is available immediately (for example in the case of a +non-asynchronous upload), the content repository should ignore this parameter. + +If the MXC has expired, the content repository should respond with `M_NOT_FOUND` +and a HTTP 404 status code. + +If the data is not available when the server chooses to respond, the content +repository returns a `M_NOT_YET_UPLOADED` error with a HTTP 504 status code. + +For the `/download` endpoint, the server could also stream data directly as it +is being uploaded. However, streaming creates several implementation and spec +complications (e.g. how to stream if the media repo has multiple workers, what +to do if the upload is interrupted), so specifying exactly how streaming works +is left for another MSC. + +## Potential issues +Other clients may time out the download if the sender takes too long to upload +media. + +## Alternatives + +## Security considerations + +The primary attack vector that must be prevented is a malicious user creating a +large number of MXC URIs and sending them to a room without uploading the +corresponding media. Clients in that room would then attempt to download the +media, holding open connections to the server and potentially exhausting the +number of available connections. + +This attack vector is stopped in multiple ways: + +1. Limits on `/create` prevent users from creating MXC URIs too quickly and also + require them to finish uploading files (or let some of their MXCs expire) + before creating new MXC URIs. + +2. Servers are free to respond to `/download` and `/thumbnail` requests before + the `timeout_ms` has been reached and respond with `M_NOT_YET_UPLOADED`. For + example, if the server is under connection count pressure, it can choose to + respond to waiting download connections with `M_NOT_YET_UPLOADED` to free + connections in the pool. + +3. Once the media is expired, servers can respond immediately to `/download` and + `/thumbnail` requests with `M_NOT_FOUND`. + +## Future work + +Future MSCs might wish to address large file uploads. One approach would be to +add metadata to the `/create` call via a query parameter (for example +`?large_file_upload=true`. Servers would have the ability to impose restrictions +on how many such "large file" uploads a user can have concurrently. For such a +situation, the server would likely send a more generous `unused_expires_at` +timestamp to allow for a long-running upload. + +## Unstable prefix +While this MSC is not in a released version of the spec, implementations should +use `fi.mau.msc2246` as a prefix and as an `unstable_features` flag in the +`/versions` endpoint. + +* `POST /_matrix/media/unstable/fi.mau.msc2246/create` +* `PUT /_matrix/media/unstable/fi.mau.msc2246/upload/{serverName}/{mediaId}` +* `?fi.mau.msc2246.timeout_ms` +* `FI.MAU.MSC2246_NOT_YET_UPLOADED` +* `FI.MAU.MSC2246_CANNOT_OVERWRITE_MEDIA` + +[matrix-spec#432]: https://github.com/matrix-org/matrix-spec/issues/432 +[MSC701]: https://github.com/matrix-org/matrix-doc/issues/701 diff --git a/proposals/2249-report-require-joined.md b/proposals/2249-report-require-joined.md new file mode 100644 index 000000000..5b1cc13c4 --- /dev/null +++ b/proposals/2249-report-require-joined.md @@ -0,0 +1,54 @@ +# MSC2249: Require users to have visibility on an event when submitting reports + +The [report API](https://matrix.org/docs/spec/client_server/r0.5.0#post-matrix-client-r0-rooms-roomid-report-eventid) +currently does not require users to be joined to the room in order to report that an +event is inappropriate. This allows anyone to report any event in any room without being joined to the room. +There is limited use (and scope for abuse) for users to call report on rooms they are not joined to, +so this proposal requires that reporting users must be joined to a room before they can report an event. + +Furthermore this proposal addresses the case where the user may not have visibility +on an event (e.g. not being able to read history in a room). + +In that case, similar logic applies as described below. + +## Proposal + +The `/rooms/{roomId}/report/{eventId}` endpoint should check to see if the authenticated user +is joined to the room in the current state of the room. If the user is not joined to the room, +the room does not exist, or the event does not exist the server should respond with: + +```json +{ + "errcode": "M_NOT_FOUND", + "error": "Unable to report event: it does not exist or you aren't able to see it." +} +``` + +where the contents of `error` can be left to the implementation. It is important to note that this response +MUST be sent regardless if the room/event exists or not as this endpoint could be used as a way to brute +force room/event IDs in order to find a room/event. + +It is not expected for homeservers to attempt to backfill an event they cannot find locally, as the user is unlikely to +have seen an event that the homeserver has not yet stored. + +If the event is redacted, reports MAY still be allowed but are dependant on the implementation. + +## Tradeoffs + +None + +## Potential issues + +This will incur a performance penalty on the endpoint as the homeserver now needs to query state in the room, however +this is considered acceptable given the potential to reduce abuse of the endpoint. + +## Security considerations + +Care should be taken not to give away information inadvertently by responding with different error codes depending +on the existence of the room, as it may give away private rooms on the homeserver. This may be somewhat unavoidable +due to the time delay for checking the existence of a room vs checking the state for a user, so implementations +MAY decide to "fuzz" the response times of the endpoint to avoid time-based attacks. +## Conclusion + +This proposal should hopefully reduce the abuse potential of the /report endpoint without significantly increasing +the complexity or performance requirements on a homeserver. diff --git a/proposals/2659-appservice-ping.md b/proposals/2659-appservice-ping.md new file mode 100644 index 000000000..90deda5ce --- /dev/null +++ b/proposals/2659-appservice-ping.md @@ -0,0 +1,105 @@ +# MSC2659: Application service ping endpoint + +## Problem +A relatively common problem when setting up appservices is the connection +between the appservice and homeserver not working in one or both directions. +If the appservice is unable to connect to the homeserver, it can simply show +the error message to the user. However, there's currently no easy way for the +appservice to know if the homeserver is unable to connect to it. This means +that the appservice might start up fine, but not actually work, because the +homeserver isn't sending events to it. + +## Proposed solution +The proposed solution is a new endpoint in homeservers that appservices can use +to trigger a ping. A new endpoint is also added to the appservice side for the +homeserver to call without any side-effects. + +Appservices can use the endpoint at startup to ensure communication works in +both directions, and show an error to the user if it doesn't. + +### `POST /_matrix/app/v1/ping` +This endpoint is on the appservice side. Like all other appservice-side +endpoints, it is authenticated using the `hs_token`. When the token is correct, +this returns HTTP 200 and an empty JSON object as the body. + +The request body contains an optional `transaction_id` string field, which +comes from the client ping request defined below. + +Appservices don't need to have any special behavior on this endpoint, but they +may use the incoming request to verify that an outgoing ping actually pinged +the appservice rather than going somewhere else. + +This proposal doesn't define any cases where a homeserver would call the ping +endpoint unless explicitly requested by the appservice (using the client +endpoint below). Therefore, appservices don't necessarily have to implement +this endpoint if they never call the client ping endpoint. + +### `POST /_matrix/client/v1/appservice/{appserviceId}/ping` +When the endpoint is called, the homeserver makes a `/_matrix/app/v1/ping` +request to the appservice. + +The request body may contain a `transaction_id` string field, which, if present, +must be passed through to the appservice `/ping` request body as-is. + +This endpoint is only allowed when using a valid appservice token, and it can +only ping the appservice associated with the token. If the token or appservice +ID in the path is wrong, the server may return `M_FORBIDDEN`. However, +implementations and future spec proposals may extend what kinds of pings are +allowed. + +In case the homeserver had backed off on sending transactions, it may treat a +successful ping as a sign that the appservice is up again and transactions +should be retried. + +#### Response +If the ping request returned successfully, the endpoint returns HTTP 200. The +response body has a `duration_ms` field containing the `/_matrix/app/v1/ping` +request roundtrip time as milliseconds. + +If the request fails, the endpoint returns a standard error response with +`errcode`s and HTTP status codes as specified below: + +* If the appservice doesn't have a URL configured, `M_URL_NOT_SET` and HTTP 400. +* For non-2xx responses, `M_BAD_STATUS` and HTTP 502. Additionally, the response + may include `status` (integer) and `body` (string) fields containing the HTTP + status code and response body text respectively to aid with debugging. +* For connection timeouts, `M_CONNECTION_TIMEOUT` and HTTP 504. +* For other connection errors, `M_CONNECTION_FAILED` and HTTP 502. + It is recommended to put a more detailed explanation in the `error` field. + +### Example flow + +1. bridge -> homeserver (request #1): `POST http://synapse:8008/_matrix/client/v1/appservice/whatsapp/ping` + * Header `Authorization: Bearer as_token` + * Body: `{"transaction_id": "meow"}` +2. homeserver -> bridge (request #2): `POST http://bridge:29318/_matrix/app/v1/ping` + * Header `Authorization: Bearer hs_token` + * Body: `{"transaction_id": "meow"}` +3. bridge -> homeserver (response to #2): 200 OK with body `{}` +4. homeserver -> bridge (response to #1): 200 OK with body `{"duration_ms": 123}` + (123 milliseconds being the time it took for request #2 to complete). + +## Alternatives + +* The ping could make an empty `/transactions` request instead of adding a new + ping endpoint. A new endpoint was found to be cleaner while implementing, and + there didn't seem to be any significant benefits to reusing transactions. +* A `/versions` endpoint could be introduced to work for both pinging and + checking what spec versions an appservice supports. However, it's not clear + that a new endpoint is the best way to detect version support (a simple flag + in the registration file may be preferable), so this MSC proposes a `/ping` + endpoint that doesn't have other behavior. +* Appservices could be switched to using websockets instead of the server + pushing events. This option is already used by some bridges, but implementing + websocket support on the homeserver side is much more complicated than a + simple ping endpoint. + +## Unstable prefix +The endpoints can be implemented as `/_matrix/app/unstable/fi.mau.msc2659/ping` +and `/_matrix/client/unstable/fi.mau.msc2659/appservice/{appserviceId}/ping`. +Error codes can use `FI.MAU.MSC2659_` instead of `M_` as the prefix. + +`fi.mau.msc2659` can be used as an `unstable_features` flag in `/versions` to +indicate support for the unstable prefixed endpoint. Once the MSC is approved, +`fi.mau.msc2659.stable` can be used to indicate support for the stable endpoint +until the spec release containing the endpoint is supported. diff --git a/proposals/2746-reliable-voip.md b/proposals/2746-reliable-voip.md new file mode 100644 index 000000000..71e1ffe99 --- /dev/null +++ b/proposals/2746-reliable-voip.md @@ -0,0 +1,345 @@ +# MSC2746: Improved Signalling for 1:1 VoIP + +Historically, Matrix has basic support for signalling 1:1 WebRTC calls which suffer a number of shortcomings: + + * If several devices try to answer the same call, there is no way for them to determine clearly + that the caller has set up the call with a different device, and no way for the caller to + determine which candidate events map to which answer. + * Hangup reasons are often incorrect. + * There is confusion and no clear guidance on how clients should determine whether an incoming + invite is stale or not. + * There is no support for renegotiation of SDP, for changing ICE candidates / hold/resume + functionality, etc. + * There is no distinction between rejecting a call and ending it, which means that in trying + to reject a call, a client can inadvertently cause a call that has been successfully set up + on a different device to be hung up. + +## Proposal +### Change the `version` field in all VoIP events to `"1"` +The version property is changed to `"1"` in all existing VoIP events +([`m.call.answer`](https://spec.matrix.org/v1.5/client-server-api/#mcallanswer), +[`m.call.candidates`](https://spec.matrix.org/v1.5/client-server-api/#mcallcandidates), +[`m.call.hangup`](https://spec.matrix.org/v1.5/client-server-api/#mcallhangup) +[`m.call.invite`](https://spec.matrix.org/v1.5/client-server-api/#mcallinvite)). Note +that this changes the type of the `version` field from an integer to a string, as +described in the [Unstable Prefix](#unstable-prefix) section. + +This will be used to determine whether devices support this new version of the protocol. For example, +clients can use this field to know whether to expect an `m.call.select_answer` event from their +opponent. If clients see events with `version` other than `0` or `"1"` (including, for example, the numeric +value `1`), they should treat these the same as if they had `version` == `"1"`. + +Note that this implies any and all future versions of VoIP events should be backwards-compatible. +If it does become necessary to introduce a non backwards-compatible VoIP spec, the intention would +be for it to simply use a separate set of event types. + +### Define the configurations of WebRTC streams and tracks + +The [spec](https://spec.matrix.org/v1.5/client-server-api/#voice-over-ip) does not currently define +the WebRTC streams and tracks that should be sent. Under this proposal, +clients are expected to send one stream with one track of kind `audio` (creating a +voice call). They can optionally send a second track in the same stream of kind +`video` (creating a video call). + +Clients implementing this specification use the first stream and will ignore any streamless tracks. Note that +in the Javascript WebRTC API, this means `addTrack()` must be passed two parameters: a track and a stream, +not just a track, and in a video call the stream must be the same for both audio and video track. + +A client may send other streams and tracks but the behaviour of the other party with respect to presenting +such streams and tracks is undefined. + +This follows the existing known implementations of v0 VoIP. + +### Add `invitee` field to [`m.call.invite`](https://spec.matrix.org/v1.5/client-server-api/#mcallinvite) +This allows for the following use cases: + * Placing a call to a specific user in a room where other users are also present. + * Placing a call to oneself. + +The field should be added for all invites where the target is a specific user, and should be set +to the Matrix user ID of that user. Invites without an `invitee` +field are defined to be intended for any member of the room other than the sender of the event. + +Clients should consider an incoming call if they see a non-expired invite event where the `invitee` field is either +absent or equal to their user's Matrix ID, however they should evaluate whether or not to ring based on their +user's trust relationship with the callers and/or where the call was placed. As a starting point, it is +suggested that clients ignore call invites from users in public rooms. It is strongly recommended that +when clients do not ring for an incoming call invite, they still display the call invite in the room and +annotate that it was ignored. + +### Add `party_id` to all VoIP events +Whenever a client first participates in a new call, it generates a `party_id` for itself to use for the +duration of the call. This needs to be long enough that the chance of a collision between multiple devices +both generating an answer at the same time generating the same party ID is vanishingly small: 8 uppercase + +lowercase alphanumeric characters is recommended. Parties in the call are identified by the tuple of +`(user_id, party_id)`. + +The client adds a `party_id` field containing this ID to the top-level of the content of all VoIP events +it sends on the call, including `m.call.invite`. Clients use this to identify remote echo of their own +events: since a user may now call themselves, they can no longer ignore events from their own user. This +field also identifies different answers sent by different clients to an invite, and matches `m.call.candidates` +events to their respective answer/invite. + +A client implementation may choose to use the device ID used in end-to-end cryptography for this purpose, +or it may choose, for example, to use a different one for each call to avoid leaking information on which +devices were used in a call (in an unencrypted room) or if a single device (ie. access token) were used to +send signalling for more than one call party. + +A grammar for `party_id` is defined [below](#specify-exact-grammar-for-voip-ids). + +### Introduce `m.call.select_answer` +This event is sent by the caller's client once it has decided which other +client to talk to, by selecting one of multiple possible incoming `m.call.answer` +events. Its `selected_party_id` field indicates the answer it's chosen. The `call_id` +and `party_id` of the caller is also included. If the callee's client sees a `select_answer` for an answer +with party ID other than the one it sent, it ends the call and informs the user the call +was answered elsewhere. It does not send any events. Media can start flowing +before this event is seen or even sent. Clients that implement previous +versions of this specification will ignore this event and behave as they did +before. + +Example: +``` +{ + "type": "m.call.select_answer", + "content": { + "version": "1", + "call_id": "12345", + "party_id": "67890", + "selected_party_id": "111213", + } +} +``` + +### Introduce `m.call.reject` + + * If the `m.call.invite` event has `version` `"1"`, a client wishing to reject the call + sends an `m.call.reject` event. This rejects the call on all devices, but if the calling + device sees an `answer` before the `reject`, it disregards the reject event and carries on. The reject has a + `party_id` just like an answer, and the caller sends a `select_answer` for it just like an + answer. If another client had already sent an answer and sees the caller select the + reject response instead of its answer, it ends the call. + * If the `m.call.invite` event has `version` `0`, the callee sends an `m.call.hangup` event. + +Example: +``` +{ + "type": "m.call.reject", + "content" : { + "version": "1", + "call_id": "12345", + "party_id": "67890", + } +} +``` + +If the calling user chooses to end the call before setup is complete, the client sends `m.call.hangup` +as previously. + +### Clarify what actions a client may take in response to an invite +The client may: + * Attempt to accept the call by sending an `m.call.answer`. + * Actively reject the call everywhere: send an `m.call.reject` as per above, which will stop the call from + ringing on all the user's devices and the caller's client will inform them that the user has + rejected their call. + * Ignore the call: send no events, but stop alerting the user about the call. The user's other + devices will continue to ring, and the caller's device will continue to indicate that the call + is ringing, and will time the call out in the normal way if no other device responds. + +### Introduce more reason codes to [`m.call.hangup`](https://spec.matrix.org/v1.5/client-server-api/#mcallhangup) + * `ice_timeout`: The connection failed after some media was exchanged (as opposed to current + `ice_failed` which means no media connection could be established). Note that, in the case of + an ICE renegotiation, a client should be sure to send `ice_timeout` rather than `ice_failed` if + media had previously been received successfully, even if the ICE renegotiation itself failed. + * `user_hangup`: Clients must now send this code when the user chooses to end the call, although + for backwards compatibility with version 0, a clients should treat an absence of the `reason` + field as `user_hangup`. + * `user_media_failed`: The client was unable to start capturing media in such a way that it is unable + to continue the call. + * `user_busy`: The user is busy. Note that this exists primarily for bridging to other networks such + as the PSTN. A Matrix client that receives a call whilst already in a call would not generally reject + the new call unless the user had specifically chosen to do so. + * `unknown_error`: Some other failure occurred that meant the client was unable to continue the call + rather than the user choosing to end it. + +### Introduce `m.call.negotiate` +This introduces SDP negotiation semantics for media pause, hold/resume, ICE restarts and voice/video +call up/downgrading. Clients should implement & honour hold functionality as per WebRTC's +recommendation: https://www.w3.org/TR/webrtc/#hold-functionality + +If both the invite event and the accepted answer event have `version` equal to `"1"`, either party may +send `m.call.negotiate` with a `description` field to offer new SDP to the other party. This event has +`call_id` with the ID of the call and `party_id` equal to the client's party ID for that call. +The caller ignores any negotiate events with `party_id` + `user_id` tuple not equal to that of the +answer it accepted and the callee ignores any negotiate events with `party_id` + `user_id` tuple not equal to that of the caller. Clients should use the `party_id` field to ignore the remote echo of their +own negotiate events. + +This has a `lifetime` field as in `m.call.invite`, after which the sender of the negotiate event +should consider the negotiation failed (timed out) and the recipient should ignore it. + +The `description` field is the same as the `offer` field in `m.call.invite` and `answer` +field in `m.call.answer` and is an `RTCSessionDescriptionInit` object as per +https://www.w3.org/TR/webrtc/#dom-rtcsessiondescriptioninit. + +Example: +``` +{ + "type": "m.call.negotiate", + "content": { + "version": "1", + "call_id": "12345", + "party_id": "67890", + "lifetime": 10000, + "description": { + "sdp": "[some sdp]", + "type": "offer", + }, + } +} +``` + +Once an `m.call.negotiate` event is received, the client must respond with another `m.call.negotiate` +event, with the SDP answer (with `"type": "answer"`) in the `description` property. + +This MSC also proposes clarifying the `m.call.invite` and `m.call.answer` events to state that +the `offer` and `answer` fields respectively are objects of type `RTCSessionDescriptionInit`. +Hence the `type` field, whilst redundant in these events, is included for ease of working +with the WebRTC API and is mandatory. Receiving clients should not attempt to validate the `type` field, +but simply pass the object into the WebRTC API. + +### Designate one party as 'polite' +In line with WebRTC perfect negotiation (https://w3c.github.io/webrtc-pc/#perfect-negotiation-example) +we introduce rules to establish which party is polite in the process of renegotiation. The callee is +always the polite party. In a glare situation, the politenes of a party is therefore determined by +whether the inbound or outbound call is used: if a client discards its outbound call in favour of +an inbound call, it becomes the polite party. + +### Add explicit recommendations for call event liveness. +`m.call.invite` contains a `lifetime` field that indicates how long the offer is valid for. When +a client receives an invite, it should use the event's `age` field in the sync response plus the +time since it received the event from the homeserver to determine whether the invite is still valid. +The use of the `age` field ensures that incorrect clocks on client devices don't break calls. +If the invite is still valid *and will remain valid for long enough for the user to accept the call*, +it should signal an incoming call. The amount of time allowed for the user to accept the call may +vary between clients. For example, it may be longer on a locked mobile device than on an unlocked +desktop device. + +The client should only signal an incoming call in a given room once it has completed processing the +entire sync response and, for encrypted rooms, attempted to decrypt all encrypted events in the +sync response for that room. This ensures that if the sync response contains subsequent events that +indicate the call has been hung up, rejected, or answered elsewhere, the client does not signal it. + +If on startup, after processing locally stored events, the client determines that there is an invite +that is still valid, it should still signal it but only after it has completed a sync from the homeserver. + +The minimal recommended lifetime is 90 seconds - this should give the user +enough time to actually pick up the call. + +### Introduce recommendations for batching of ICE candidates +Clients should aim to send a small number of candidate events, with guidelines: + * ICE candidates which can be discovered immediately or almost immediately in the invite/answer + event itself (eg. host candidates). If server reflexive or relay candidates can be gathered in + a sufficiently short period of time, these should be sent here too. A delay of around 200ms is + suggested as a starting point. + * The client should then allow some time for further candidates to be gathered in order to batch them, + rather than sending each candidate as it arrives. A starting point of 2 seconds after sending the + invite or 500ms after sending the answer is suggested as a starting point (since a delay is natural + anyway after the invite whilst the client waits for the user to accept it). + +### Mandate the end-of-candidates candidate +Define that an ICE candidate whose value is the empty string means that no more ICE candidates will +be sent, and mandate that clients must send such a candidate in an `m.call.candidates` message. +The WebRTC spec requires browsers to generate such a candidate, however note that at time of writing, +not all browsers do (Chrome does not, but does generate an `icegatheringstatechange` event). The +client should send any remaining candidates once candidate generation finishes, ignoring timeouts above. +This allows bridges to batch the candidates together when bridging to protocols that don't support +trickle ICE. + +### Add DTMF +Add that Matrix clients can send DTMF as specified by WebRTC. The WebRTC standard as of August +2020 does not support receiving DTMF but a Matrix client can receive and interpret the DTMF sent +in the RTP payload. + +### Specify exact grammar for VoIP IDs +`call_id`s and the newly introduced `party_id` are explicitly defined to be between 1 +and 255 characters long, consisting of the characters `[0-9a-zA-Z._~-]`. + +(Note that this matches the grammar of 'opaque IDs' from +[MSC1597](https://github.com/matrix-org/matrix-spec-proposals/blob/rav/proposals/id_grammar/proposals/1597-id-grammar.md#opaque-ids), +and that of the `id` property of the + [`m.login.sso` flow schema](https://spec.matrix.org/v1.5/client-server-api/#definition-mloginsso-flow-schema).) + +### Specify behaviour on room leave +If the client sees the user it is in a call with leave the room, the client should treat this +as a hangup event for any calls that are in progress. No specific requirement is given for the +situation where a client has sent an invite and the invitee leaves the room, but the client may +wish to treat it as a rejection if there are no more users in the room who could answer the call +(eg. the user is now alone or the `invitee` field was set on the invite). + +The same behaviour applies when a client is looking at historic calls. + +### Clarify that supported codecs should follow the WebRTC spec +The Matrix spec does not mandate particular audio or video codecs, but instead defers to the +WebRTC spec. A compliant matrix VoIP client will behave in the same way as a supported 'browser' +in terms of what codecs it supports and what variants thereof. The latest WebRTC specification +applies, so clients should keep up to date with new versions of the WebRTC specification whether +or not there have been any changes to the Matrix spec. + +## Potential issues + * The ability to call yourself makes the protocol a little more complex for clients to implement, + and is somewhat of a special case. However, some of the necessary additions are also required for + other features so this MSC elects to make it possible. + * Clients must make a decision on whether to ring for any given call: defining this in the spec + would be cumbersome and would limit clients' ability to use reputation-based systems for this + decision in the future. However, having a call ring on one client and not the other because one + had categorised it as a junk call and not the other would be confusing for the user. + +## Alternatives + * This MSC does not allow for ICE negotiation before the user chooses to answer the call. This can + make call setup faster by allowing connectivity to be established whilst the call is ringing. This + is problematic with Matrix since any device or user could answer the call, so it is not known which + device is going to answer before the user chooses to answer. It would also leak information on which + of a user's devices were online. + * We could define that the ID of a call is implicitly the event ID of the invite event rather than + having a specific `call_id` field. This would mean that a client would be unable to know the ID of + a call before the it received the response from sending the invite event, which could complicate + implementations. There is probably no compelling reason to change this. + * `m.call.select_answer` was chosen such that its name reflect the intention of the event. `m.call.ack` + is more succinct and mirrors SIP, but this MSC opts for the more descriptive name. + * This MSC elects to allow invites without an `invitee` field to mean a call for anyone in the room. + This could be useful for hunt group style semantics where an incoming call causes many different + users' phones to ring and any one of them may pick up the call. This does mean clients will need + to not blindly ring for any call invites in any room, since this would make unsolicited calls + easy in public rooms. We could opt to leave this out, or make it more explicit with a specific value + for the `invitee` field. + * `party_id` is one of many potential solutions: callees could add `answer_id`s to their events and + callers could be identified by the lack of an `answer_id`. An explicit field on every event may be + easier to comprehend, less error-prone and clearer in the backwards-compatibility scenario. + * We could make `party_id`s more prescriptive, eg. the caller could always have a `party_id` of the + empty string, the word `caller` or equal to the `call_id`, which may make debugging simpler. + * To allow for bridging into protocols that don't support trickle ICE, this proposal requires that + clients send an empty candidate to signal the end of candidates. This means it will be up to bridges + to buffer the invite and edit the SDP to add the candidates once they arrive, adding complexity to + bridges. The alternative would be a discovery mechanism so clients could know whether a callee supports + trickle ICE before calling, and disable it if so. This would add complexity to every Matrix client as + well as having to assume that all current clients did not, disabling trickle ICE everywhere until clients + support the discovery mechanism. The mechanism would also have to be per-user which would make sense for + bridged users, but not where some of a users devices support trickle ICE and some do not. + +## Security considerations + * IP addresses remain in the room in candidates, as they did in the previous version of the spec. + This is not ideal, but alternatives were either sending candidates over to-device messages + (would slow down call setup because a target device would have to be established before sending + candidates) or redacting them afterwards (the volume of events sent during calls can already + cause rate limiting issues and this would exacerbate this). + * Clients must take care to not ring for any call, as per the 'alternatives' section. + +## Unstable prefix +Since VoIP events already have a 'version' field, we would ideally use a string, namespaced version during +development, but this field is defined to be an int in version 0. This MSC proposes changing the version +field to a string so that this namespacing can be used for future changes. Since there is no other easy way +to namespace events whilst in development and ensure interoperability, we have chosen not to use an unstable +prefix for this change, on the understanding that in future we will be able to use the string `version` field +for the unstable prefix. + +For backwards compatibility, strongly typed implementations should allow for +`version` to either be a string or the integer `0`. diff --git a/proposals/3860-media-download-redirect.md b/proposals/3860-media-download-redirect.md new file mode 100644 index 000000000..90092aee9 --- /dev/null +++ b/proposals/3860-media-download-redirect.md @@ -0,0 +1,61 @@ +# MSC3860: Media Download Redirects + +Currently the media download endpoints must return either a 200 with content or error responses. This +means the media server instance must stream the data from wherever it is stored, which is likely not +local to itself. Allowing redirects on these endpoints would make it possible for the media repo to +tell clients/servers to pull data direct from the source, e.g. a CDN. + +Additionally redirects could be used to avoid downloading media from untrusted homeservers. Currently +users can make their homeserver download, cache and proxy any matrix mid that I want, including +bad/illegal content. Allowing for a 307 redirect would permit cautious server operators to not +store and provide any media that floats in the matrixverse, but just refer to the "original" media. + +## Proposal + +This MSC proposes that a 307 or 308 redirect code is allowed and followed according to the `Location` +header. It is possible some clients would already follow these which needs to be confirmed. Specific +endpoints in question ([current spec link for these](https://spec.matrix.org/v1.6/client-server-api/#get_matrixmediav3downloadservernamemediaid)): + ++ `/_matrix/media/v3/download/{serverName}/{mediaId}` ++ `/_matrix/media/v3/download/{serverName}/{mediaId}/{fileName}` ++ `/_matrix/media/v3/thumbnail/{serverName}/{mediaId}` + +To prevent breaking clients that don't properly follow the redirect response this functionality will +be enabled by a query string flag `allow_redirect=true`. So specifically in the above cases if a +client respects redirect responses it can make requests like so to the media endpoints: + ++ `/_matrix/media/v3/download/{serverName}/{mediaId}?allow_redirect=true` ++ `/_matrix/media/v3/download/{serverName}/{mediaId}/{fileName}?allow_redirect=true` ++ `/_matrix/media/v3/thumbnail/{serverName}/{mediaId}?allow_redirect=true` + +In the case where a client wishes not to redirect (either implicitly with no parameter or explicitly +providing `allow_redirect=false`) the server must continue to serve media directly with no redirect. + +## Potential Issues + +None for clients, as opt-in functionality this change is 100% backwards compatible. + +With redirects it becomes easier to force another homeserver to be your CDN, which isn't great +(clients could already do that, but not without recompiling). + +Redirects also potentially allow changing of media underneath the users as different redirects could +be returned over time. It is worth noting that a badly behaving media server can already just return +different content as well. + +## Alternatives + +None at this time. + +## Security Considerations + +A media repo could redirect requests to a bad actor, although this would make the primary media +repo itself a bad actor, thus this does not present any increased security issues. + +## Unstable Prefix + +Until this functionality has landed in the spec, the `allow_redirect` query +parameter should be prefixed with `com.beeper.msc3860.`: + +``` +?com.beeper.msc3860.allow_redirect=true +``` diff --git a/proposals/3882-login-token-request.md b/proposals/3882-login-token-request.md new file mode 100644 index 000000000..7372ad97c --- /dev/null +++ b/proposals/3882-login-token-request.md @@ -0,0 +1,177 @@ +# MSC3882: Allow an existing session to sign in a new session + +In [MSC3906](https://github.com/matrix-org/matrix-spec-proposals/pull/3906) a proposal is made to allow a user to login +on a new device using an existing device by means of scanning a QR code. + +In order to support the above proposal a mechanism is needed where by the new device can obtain a new access token that +it can use with the Client-Server API. + +It is proposed that the current `m.login.token` mechanism is extended to allow the issuance of a login token by an +existing client session. + +## Proposal + +### New API endpoint POST /login/get_token + +Add a new optional POST endpoint to the Client-Server API that issues a single-use, time-limited `m.login.token` token: + +`POST /_matrix/client/v1/login/get_token` + +The client should send an empty JSON object for the body of the `POST` request (apart from +the `auth` property used in user-interactive authentication). + +As detailed in the security selection below, this new endpoint should be protected by user interactive authentication +(UIA) as detailed in the existing +["User-interactive API in the REST API"](https://spec.matrix.org/v1.5/client-server-api/#user-interactive-api-in-the-rest-api) +section of the spec. + +Once UIA has been completed a `200` response with JSON body is returned. The body contains the following fields: + +- `login_token` - required, the token to use with `m.login.token` +- `expires_in_ms` - required, how long until the token expires in milliseconds + +An example response is as follows: + +```http +HTTP/1.1 200 OK +Content-Type: application/json +``` + +```json +{ + "login_token": "", + "expires_in_ms": 120000 +} +``` + +This token can then be used as per the existing [Login spec](https://spec.matrix.org/v1.6/client-server-api/#login) as follows: + +```http +POST /_matrix/client/v3/login HTTP/1.1 +Content-Type: application/json +``` + +```json +{ + "type": "m.login.token", + "token": "" +} +``` + +### Determining the availability of the new API endpoint + +As this new API endpoint is optional, clients should determine whether the endpoint is available +before prompting the user to try using it. + +There are two usage scenarios to consider: + +1. The user wishes to sign in on a Matrix client. +2. The user wishes to use an already signed in Matrix client to sign in another client. + +In scenario 2 the client is already authenticated. For scenario 1 the client is not yet authenticated. + +#### Scenario 1: The user wishes to sign in on a Matrix client + +The client wants to determine if it *may* be possible to sign in by getting a login token from an +existing session. + +It is proposed that the unauthenticated client can determine if the new API endpoint *may* be available +as part of the existing +[`GET /_matrix/client/v3/login`](https://spec.matrix.org/v1.6/client-server-api/#get_matrixclientv3login) +API endpoint. + +As the `m.login.token` mechanism is used to redeem the login token, the client can first determine if the +`m.login.token` is advertised as a flow in the `GET /_matrix/client/v3/login` response. Then it can check a +new boolean field `get_login_token` to determine if the capability *may* be available. + +An example of the proposed `GET /_matrix/client/v3/login` response is: + +```json +{ + "flow": [ + { + "type": "m.login.token", + "get_login_token": true + } + ] +} +``` + +In this case the mechanism could be available and so the client could prompt the user to try using it. + +#### Scenario 2: The user wishes to use an already signed in Matrix client to sign in another client + +The client is already authenticated. The client can determine whether it is able and allowed to sign in +another client by checking the +[capabilities](https://spec.matrix.org/v1.6/client-server-api/#capabilities-negotiation) +advertised by the homeserver. + +The unauthenticated client can also determine whether the new API endpoint is available +via the [capability negotiation](https://spec.matrix.org/v1.6/client-server-api/#capabilities-negotiation) +mechanism. + +The homeserver can then decide on a per user basis if the capability is available or not. For example, +it could implement a policy based on some risk criteria around the user’s account, session, or device. + +A new capability `m.get_login_token` is proposed. This capability has a single boolean flag, `enabled`, to +denote whether the `/login/get_token` API is available or not. + +An example of the capability API’s response for this capability is: + +```json +{ + "capabilities": { + "m.get_login_token": { + "enabled": true + } + } +} +``` + +## Potential issues + +None identified. + +## Alternatives + +If Matrix was already using OIDC as per [MSC3861](https://github.com/matrix-org/matrix-spec-proposals/pull/3861) then we +could use the device authorization grant flow which allows for a new device to be signed in using an existing device. + +## Security considerations + +A malicious client could use the mechanism to spawn more than one session. The following mitigations should be applied: + +1. The homeserver must only allow the token to be used for a single login. If the user wishes to sign in multiple + additional clients a token must be issued for each client. + +2. The homeserver should enforce +[user interactive authentication](https://spec.matrix.org/v1.6/client-server-api/#user-interactive-authentication-api) +by default for the new endpoint. The purpose being that consent is obtained from the user for each additional client. + +3. The homeserver should enforce rate-limiting in accordance with the existing +[spec](https://spec.matrix.org/v1.6/client-server-api/#rate-limiting). It may be appropriate for the homeserver admin to +to configure a low limit ("low" relative to other enforced limits). For example, a rate of once per minute could be appropriate. + +n.b. A homeserver admin may deem that they have suitable protections in place and offer the endpoint without UIA auth as described +in the existing spec: + +> A request to an endpoint that uses User-Interactive Authentication never succeeds without auth. Homeservers may allow requests +> that don’t require auth by offering a stage with only the m.login.dummy auth type, but they must still give a 401 response to +> requests with no auth data. + +## Unstable prefix + +While this feature is in development the following unstable prefixes should be used: + +- API endpoint `/_matrix/client/v1/login/get_token` => `/_matrix/client/unstable/org.matrix.msc3882/login/get_token` +- capability `m.get_login_token` => `org.matrix.msc3882.get_login_token` +- login flow field `get_login_token` => `org.matrix.msc3882.get_login_token` + +For reference - an earlier revision of this proposal used an unstable endpoint of +`/_matrix/client/unstable/org.matrix.msc3882/login/token` with an unstable feature advertised +in the response to `GET /_matrix/client/versions` as `org.matrix.msc3882` +set to `true`. This may be referred to as "revision zero" in existing implementations. + +## Dependencies + +None. diff --git a/proposals/3952-intentional-mentions.md b/proposals/3952-intentional-mentions.md new file mode 100644 index 000000000..99fd4175d --- /dev/null +++ b/proposals/3952-intentional-mentions.md @@ -0,0 +1,633 @@ +# MSC3952: Intentional Mentions + +Mentioning other users on Matrix is difficult -- it is not possible to know if +[mentioning a user by display name or Matrix ID](https://github.com/matrix-org/matrix-spec/issues/353) +will count as a mention, but is also too easy to mistakenly mention a user. + +(Note that throughout this proposal "mention" is considered equivalent to a "ping" +or highlight notification.) + +Some situations that result in unintentional mentions include: + +* Replying to a message will re-issue pings from the initial message due to + [fallback replies](https://spec.matrix.org/v1.5/client-server-api/#fallbacks-for-rich-replies). + * A user without the power level to send `@room` can abuse this by including + `@room` in a message and getting a user with the appropriate power levels + to reply to them. +* Each time a message is edited the new version will be re-evaluated for mentions. +* Mentions occurring [in spoiler contents](https://github.com/matrix-org/matrix-spec/issues/16) + or [code blocks](https://github.com/matrix-org/matrix-spec/issues/15) are + evaluated. +* If the [localpart of your Matrix ID is a common word](https://github.com/matrix-org/matrix-spec-proposals/issues/3011) + then the push rule matching usernames (`.m.rule.contains_user_name`) matches + too often (e.g. Travis CI matching if your Matrix ID is `@travis:example.org`). +* If the [localpart or display name of your Matrix ID matches the hostname](https://github.com/matrix-org/matrix-spec-proposals/issues/2735) + (e.g. `@example:example.org` receives notifications whenever `@foo:example.org` + is replied to). + +As a sender you do not know if including the user's display name or Matrix ID would +even be interpreted as a mention (see [issue 353](https://github.com/matrix-org/matrix-spec/issues/353)). +This results in some unexpected behavior and bugs: + +* Matrix users use "leetspeak" when sending messages to avoid mentions (e.g. + referring to M4tthew instead of Matthew). +* It is impossible to ping one out of multiple people with the same localpart + (or display name). +* Since the relation between `body` and `formatted_body` is ill-defined and + ["pills" are converted to display names](https://github.com/matrix-org/matrix-spec/issues/714), + this can result in missed messages. [^1] + +There are also some other related bugs: + +* Matrix users will append emoji or other unique text in their display names to + avoid unintentional pings. +* Bridging mentions is suboptimal since they [use display names](https://github.com/matrix-org/matrix-spec/issues/353#issuecomment-1055809364) + as a workaround, e.g.: + * It breaks the contract that bridges will not mutate the content of messages. + * For some protocols, bridges need try to figure out if every message contains + any of the possible nicknames of room members. +* If a user changes their display name in a room, + [they might not be mentioned unless the historical display name](https://github.com/matrix-org/matrix-spec/issues/353#issuecomment-1055809372) + is used while processing push rules. + +## Background + +Mentions are powered by two of the default push rules that search an event's +`content.body` property for the current user's display name +([`.m.rule.contains_display_name`](https://spec.matrix.org/v1.5/client-server-api/#default-override-rules)) +or the localpart of their Matrix ID ([`.m.rule.contains_user_name`](https://spec.matrix.org/v1.5/client-server-api/#default-content-rules)). + +There's also a [section about "user and room mentions"](https://spec.matrix.org/v1.5/client-server-api/#user-and-room-mentions) +which defines that messages which mention the current user in the `formatted_body` +of the message should be colored differently: + +> If the current user is mentioned in a message (either by a mention as defined +> in this module or by a push rule), the client should show that mention differently +> from other mentions, such as by using a red background color to signify to the +> user that they were mentioned. + +## Proposal + +The existing push rules for user and room mentions are deprecated and new rules, +which use a property specific for mentions[^2], are added to make mentions simpler +and more reliable for users. + +### New event property + +A new `m.mentions` property is added to the event content; it is an object with two +optional properties: + +* `user_ids`: an array of strings consisting of Matrix IDs to mention. +* `room`: a boolean, true indicates an "@room" mention. Any other value or the + property missing is interpreted as not an "@room" mention. + +It is valid to include both the `user_ids` and `room` properties. + +It is recommended that homeservers reject locally created events with an invalid +`m.mentions` property with an error with a status code of `400` and an errcode of +`M_INVALID_PARAM`. + +Clients add a Matrix ID to the `user_ids` array whenever composing a message which +includes an intentional mention, such as a ["pill"](https://spec.matrix.org/v1.5/client-server-api/#user-and-room-mentions). +Clients set the `room` value to `true` when making a room-wide announcement. Clients +should also set these values at other times when it is obvious the user intends to explicitly +mention a user.[^3] + +The `m.mentions` property is part of the plaintext event body and should be encrypted +into the ciphertext for encrypted events. + +### New push rules + +Two new default push rule are added. + +The `.m.rule.is_user_mention` override push rule would appear directly +before the `.m.rule.contains_display_name` push rule: + +```json +{ + "rule_id": ".m.rule.is_user_mention", + "default": true, + "enabled": true, + "conditions": [ + { + "kind": "event_property_contains", + "key": "content.m\\.mentions.user_ids", + "value": "[the user's Matrix ID]" + } + ], + "actions": [ + "notify", + { + "set_tweak": "sound", + "value": "default" + }, + { + "set_tweak": "highlight" + } + ] +} +``` + +(Note: `\\.` would become a single logical backslash followed by a dot since the +above is in JSON-representation. See +[MSC3873](https://github.com/matrix-org/matrix-spec-proposals/pull/3873).) + +The `.m.rule.is_room_mention` override push rule would appear directly +before the `.m.rule.roomnotif` push rule: + +```json +{ + "rule_id": ".m.rule.is_room_mention", + "default": true, + "enabled": true, + "conditions": [ + { + "kind": "event_property_is", + "key": "content.m\\.mentions.room", + "value": true + }, + { + "kind": "sender_notification_permission", + "key": "room" + } + ], + "actions": [ + "notify", + { + "set_tweak": "highlight" + } + ] +} +``` + +An example event matching both `.m.rule.is_user_mention` (for `@alice:example.org`) +and `.m.rule.is_room_mention` is provided below: + +```json +{ + "content": { + "body": "This is an example mention @alice:example.org", + "format": "org.matrix.custom.html", + "formatted_body": "This is an example mention Alice", + "msgtype": "m.text", + "m.mentions": { + "user_ids": ["@alice:example.org"], + "room": true + } + }, + "event_id": "$143273582443PhrSn:example.org", + "origin_server_ts": 1432735824653, + "room_id": "!somewhere:over.the.rainbow", + "sender": "@example:example.org", + "type": "m.room.message", + "unsigned": { + "age": 1234 + } +} +``` + +### Client behavior + +The overall user experience is not modified, beyond improving explicitness and +reducing unintended mentions. + +For example, it is common that a client will show an event with a mention in a +different color (and denote the current user's "pill", as a way of showing the +user *why* they were mentioned). This behavior is unchanged. + +There are two variations that clients should take into account when decorating +messages for mentions, however: + +* The presence of a user's "pill" in a message no longer implies it is a mention. +* This makes it easier to mention users without including their "pill" in a + message (see [Abuse Potential](#abuse-potential) for ideas to combat this). + +### Backwards compatibility + +The [`.m.rule.contains_display_name`](https://spec.matrix.org/v1.5/client-server-api/#default-override-rules), +[`.m.rule.contains_user_name`](https://spec.matrix.org/v1.5/client-server-api/#default-content-rules), +and [`.m.rule.roomnotif`](https://spec.matrix.org/v1.5/client-server-api/#default-override-rules) +push rules are to be deprecated. + +To avoid unintentional mentions these rules are modified to only apply when the +`m.mentions` property is missing; clients should provide at least an empty `m.mentions` property on +every message to avoid the unintentional mentions discussed in the introduction. + +A future room version may wish to disable the legacy push rules: clients would +no longer be required to include the `m.mentions` property on every event. It +maybe convenient to do this when extensible events are adopted (see +[MSC3932](https://github.com/matrix-org/matrix-spec-proposals/pull/3932)). + +After acceptance, it is likely for there to be disagreement about which push rules are +implemented: legacy clients and homeservers may not yet have deprecated the +`.m.rule.contains_display_name`, `.m.rule.contains_user_name`, and `.m.rule.roomnotif` +push rules, while up-to-date clients and homeservers will support the +`.m.rule.is_user_mention` and `.m.rule.is_room_mention` push rules. It is expected +that both sets of push rules will need to be supported for a period of time, but +at worst case should simply result in the current behavior (documented in the preamble). + +If users wish to continue to be notified of messages containing their display name +it is recommended that clients create a specific keyword rule for this, e.g. a +`content` rule of the form: + +```json +{ + "actions": [ + "notify", + { + "set_tweak": "sound", + "value": "default" + }, + { + "set_tweak": "highlight" + } + ], + "pattern": "alice", + "rule_id": "alice", + "enabled": true +} +``` + +### Impact on replies + +Users are notified of replies via the `.m.rule.contains_display_name` or the +`.m.rule.contains_user_name` push rule matching the +[rich reply fallback](https://spec.matrix.org/v1.6/client-server-api/#fallbacks-for-rich-replies). +Unfortunately these push rules will be disabled for events which contain the +`m.mentions` property, i.e. all newly created events (see +[above](#backwards-compatibility)). Clients should include the sender of the event +being replied to as well as any mentioned users in that event (excluding yourself) +in the new event's `m.mentions` property. The `room` property MUST NOT be copied over. + +This signals that it is the *intention* of the sender to mention all of those people. +This behavior may not make sense in all situations (e.g. an email-like client could +provide both a "reply" and "reply all", while a microblogging client may wish to +provide a "quote reply", dropping all mentions from the original event) and clients +may wish to allow users to modify the list of mentioned users. + +For example, if there is an event: + +```json5 +{ + "sender": "@dan:example.org", + "event_id": "$initial_event", + "content": { + "body": "Alice: Have you heard from Bob?", + "m.mentions": { + "user_ids": ["@alice:example.org", "@bob:example.org"] + } + }, + // other fields as required by events +} +``` + +And a reply from Alice: + +```json5 +{ + "content": { + "body": "> <@dan:example.org> Alice: Have you heard from Bob?\n\nNo, but I saw him with Charlie earlier.", + "m.mentions": { + "user_ids": [ + // Include the sender of $initial_event (optional). + "@dan:example.org", + // The users mentioned, minus yourself (optional). + "@bob:example.org", + // New mentions, as normal. + "@charlie:example.org" + ] + }, + "m.relates_to": { + "m.in_reply_to": { + "event_id": "$initial_event" + } + } + }, + // other fields as required by events +} +``` + +If a user wishes to be notified of *all replies* to their messages, other solutions +should be investigated, such as [MSC3664](https://github.com/matrix-org/matrix-spec-proposals/pull/3664). +This would give more equal power to both senders and receivers of events. + +### Impact on edits + +Similarly to [replies](#impact-on-replies), users are notified of message edits +via the `.m.rule.contains_display_name` or the `.m.rule.contains_user_name` push +rule matching the [fallback content](https://spec.matrix.org/v1.6/client-server-api/#event-replacements). +Generally this is undesirable and users do not need to be notified for the same +message multiple times (e.g. if a user is fixing a typo). + +Replacement events may have `m.mentions` properties in two locations: + +* One at the top-level of the `content`, which should contain any users to mention + *for this edit*. +* One inside the `m.new_content` property, which should contain the full list of + mentioned users in any version of the event, unless a mention is removed + (see below). + +It is recommended that clients use an empty top-level `m.mentions` property when +editing an event, *unless* the edit is significant or if additional users are +mentioned in the latest version. + +For example, if there is an event: + +```json5 +{ + "sender": "@dan:example.org", + "event_id": "$initial_event", + "content": { + "body": "Hello Alice!", + "m.mentions": { + "user_ids": ["@alice:example.org"] + } + }, + // other fields as required by events +} +``` + +And an edit after realizing that Bob is also in the room: + +```json5 +{ + "content": { + "body": "* Hello Alice & Bob!", + "m.mentions": { + "user_ids": [ + // Include only the newly mentioned user. + "@bob:example.org" + ] + }, + "m.new_content": { + "body": "Hello Alice & Bob!", + "m.mentions": { + "user_ids": [ + // Include all mentioned users. + "@alice:example.org", + "@bob:example.org" + ] + }, + }, + "m.relates_to": { + "rel_type": "m.replace", + "event_id": "$initial_event" + } + }, + // other fields as required by events +} +``` + +Mentions can also be removed as part of an edit. In this case, the top-level `m.mentions` +property would not include the removed user IDs (you cannot cancel the notification from +the previous event) or any previously notified users, and the removed user would also be +removed from the `m.new_content` proprerty's copy of `m.mentions`. + +This should limit duplicate, unnecessary notifications for users. If a user wishes +to receive notifications for edits of events they were mentioned in then they +could setup a push rule for the `content.m\\.new_content.m\\.mentions` property +or potentially leverage [MSC3664](https://github.com/matrix-org/matrix-spec-proposals/pull/3664). + +This implies that: + +* If a client highlights a message visually (e.g. by coloring it red), then it + should look at the `m.mentions` under `m.new_content` for edited messages. + Otherwise, in the example above, Alice would not see the message as red, even + though the intent was for her to be mentioned. +* Any sort of processing of push rules, e.g. to display a notification (sound, + toast, push notification), should occur without any special rules. I.e. the + `.m.rule.is_user_mention` and `.m.rule.is_room_mention` should look at the + `m.mentions` directly under `content` and not match for Alice. + +### Impact on bridging + +For protocols with a similar mechanism for listing mentioned users this should +strengthen the bridging contract as it enables bridges to stop mutating the +content of messages. The bridge should be able to map from the remote user ID +to the bridged user ID and include that in the `m.mentions` property of the +Matrix event & the proper field in the bridged protocol[^4]. + +For bridged protocols that do not have this mechanism, the bridge will only +be able to stop mutating content on messages bridged *into* Matrix. Messages +bridged out of Matrix will still need to embed the mention into the text +content.[^5] + +## Potential issues + +### Abuse potential + +This proposal makes it trivial to "hide" mentions since it does not require the +mentioned Matrix IDs to be part of the displayed text. This is only a limitation +for current clients: mentions could be exposed in the user interface directly. +For example, a de-emphasized "notified" list could be shown on messages, similar +to CCing users on an e-mail. + +Although not including mentions in the displayed text could be used as an abuse +vector, it does not enable additional malicious behavior than what is possible +today. From discussions and research while writing this MSC there are moderation +benefits to using a separate property for mentions: + +* The number of mentions is trivially limited by moderation tooling, e.g. it may + be appropriate for a community room to only allow 10 mentions. Events not abiding + by this could be rejected automatically (or users could be banned automatically). +* Various forms of "mention bombing" are no longer possible. +* It is simpler to collect metrics on how mentions are being used (it is no longer + necessary to process the textual `body` for every user's display name and local + part). + +Overall this proposal seems to be neutral or positive in the ability to combat +malicious behavior. + +### Encrypted mentions & `/notifications` + +A previous version of this proposal (and the alternative [MSC1796](https://github.com/matrix-org/matrix-spec-proposals/pull/1796)) +suggested leaving the `m.mentions` property in cleartext. This was +[deemed too large of a metadata leak](https://github.com/matrix-org/matrix-spec-proposals/pull/3952#discussion_r1112154200) +and removed from this proposal (and MSC1796 was closed). A downside of this is +that homeservers (still) will not be able to differentiate between notifications +and mentions in many cases. + +This mostly affects how often homeservers push to devices (see +[MSC3996](https://github.com/matrix-org/matrix-spec-proposals/pull/3996) +for more information), but also means that the `/notifications?only=highlight` +API is not useful in encrypted rooms. + +## Future extensions + +### Combating abuse + +Some ideas for combating abuse came from our discussion and research which are +worth sharing. These ideas are not a requirement for implementing this MSC, and +generally do not depend on it. (They could be implemented today with enough effort.) + +It was recommended that clients could expose *why* an event has caused a notification +and give users inline tools to combat abuse. For example, a client might show a tooltip: + +> The sender of the message (`@alice:example.org`) mentioned you in this event. +> +> Block `@alice:example.org` from sending you messages? `[Yes]` `[No]` + +Additionally, if a user sending a message is about to mention many people it can +be useful to confirm whether they wish to do that (or prompt them to do an `@room` +mention instead). + +Moderators may find tooling to quickly find messages which mention many users +useful in combating mention spammers. (Note that this should be made easier by +this MSC.) + +A future MSC might wish to explore features for trusted contacts or soft-ignores +to give users more control over who can generate notifications. + +### Muted except for mentions push rules + +It might be desirable to have a "muted-except-for-mentions" feature for large (encrypted) +rooms. This is particularly useful on iOS where a push notification can be decrypted +via a background process but *cannot* be suppressed. This means it is not possible +for the client to handle this feature and it must be handled on the server, unfortunately +this would not be possible with the current proposal since the `m.mentions` +property is encrypted (and the server cannot act on it). + +Solving this problem is left to a future MSC, such as [MSC3996](https://github.com/matrix-org/matrix-spec-proposals/pull/3996) +which builds on this proposal. + +### Pillifying `@room` + +Some clients attempt to create a "pill" out of `@room` mentions, but this is not +a requirement of the Matrix specification. The current [user and rooms mentions](https://spec.matrix.org/v1.5/client-server-api/#user-and-room-mentions) +section could be expanded for this situation. + +### Extensible events + +Handling of this property in [MSC1767](https://github.com/matrix-org/matrix-doc/pull/1767)-style +extensible events is deliberately left for a future MSC to address, if needed. + +### Role mentions + +It is possible to add additional properties to the `m.mentions` object, e.g. a foreseeable +usecase would be a `roles` property which could include values such as `admins` or +`mods`. Defining this in detail is left to a future MSC. + +### Cancelling notifications + +It might be useful for a future MSC to investigate cancelling notifications if a +user's mention is removed while [editing events](#impact-on-edits). This could +be quite difficult as it is unclear if the mentioned user has already received +the notification or not. + +## Alternatives + +### Prior proposals + +There are a few prior proposals which tackle subsets of the above problem: + +* [MSC1796](https://github.com/matrix-org/matrix-spec-proposals/pull/1796): + similar to the proposal in this MSC, but limited to encrypted events (and kept + in cleartext). +* [MSC2463](https://github.com/matrix-org/matrix-spec-proposals/pull/2463): + excludes searching inside a Matrix ID for localparts / display names of other + users. +* [MSC3517](https://github.com/matrix-org/matrix-spec-proposals/pull/3517): + searches for Matrix IDs (instead of display name / localpart) and only searches + specific event types & properties. +* [Custom push rules](https://o.librepush.net/aux/matrix_reitools/pill_mention_rules.html) + to search for matrix.to links instead of display name / localpart. + +
+ + The above generates a new push rule to replace `.m.rule.contains_display_name` + and `.m.rule.contains_user_name`: + + ```json + { + "conditions": [ + { + "kind": "event_match", + "key": "content.formatted_body", + "pattern": "*https://matrix.to/#/@alice:example.org*" + } + ], + "actions" : [ + "notify", + { + "set_tweak": "sound", + "value": "default" + }, + { + "set_tweak": "highlight" + } + ] + } + ``` + +
+ +The last two proposals use a similar idea of attempting to find "pills" in the +`formatted_body`, this has some downsides though: + +* It doesn't allow for hidden mentions, which can be useful in some situations. +* It does not work for event types other than `m.room.message`. + +It also adds significant implementation complexity since the HTML messages must +now be parsed for notifications. This is expensive and introduces potential +security issues. + +### Room version for backwards compatibility + +Alternative backwards compatibility suggestions included using a new room version, +similar to [MSC3932](https://github.com/matrix-org/matrix-spec-proposals/pull/3932) +for extensible events. This does not seem like a good fit since room versions are +not usually interested in non-state events. It would additionally require a stable +room version before use, which would unnecessarily delay usage. Another MSC +can address this concern, such as in the extensible events series, if +desirable to be gated by a room version for a "clean slate" approach. + +## Security considerations + +None not already described. + +## Unstable prefix + +During development the following mapping will be used: + +| What | Stable | Unstable | +|---------------------|-------------------|--------------------------------------| +| Event property | `m.mentions` | `org.matrix.msc3952.mentions` | +| Push rule ID | `.m.rule.*` | `.org.matrix.msc3952.*` | + +The server will include the `org.matrix.msc3952_intentional_mentions` flag in the +`unstable_features` array of the `/versions` endpoint. If a client sees this flag +it can choose to apply the deprecation logic discussed in the +[backwards compatibility](#backwards-compatibility) section. + +## Dependencies + +This depends on the following (accepted) MSCs: + +* [MSC3758](https://github.com/matrix-org/matrix-spec-proposals/pull/3758): Add `event_property_is` push rule condition kind +* [MSC3873](https://github.com/matrix-org/matrix-spec-proposals/pull/3873): event_match dotted keys +* [MSC3966](https://github.com/matrix-org/matrix-spec-proposals/pull/3966): `event_property_contains` push rule condition + + + +[^1]: It is [defined as](https://spec.matrix.org/v1.5/client-server-api/#mroommessage-msgtypes) +the the "plain text version of the HTML [`formatted_body`] should be provided in the `body`", +but there is no particular algorithm to convert from HTML to plain text *except* +when converting mentions, where the +[plain text version uses the link anchor, not the link](https://spec.matrix.org/v1.5/client-server-api/#client-behaviour-26). + +[^2]: As proposed in [issue 353](https://github.com/matrix-org/matrix-spec/issues/353). + +[^3]: Note that this isn't really a change in behavior, it is just making the behavior +explicit. It is expected that users already considered "pilled" users to be mentions, +and it was more unexpected when non-pilled users *were* mentioned. This MSC fixes the +latter case. + +[^4]: Some protocols which provide structured data for mentions include +[Twitter](https://developer.twitter.com/en/docs/twitter-api/data-dictionary/object-model/tweet), +[Mastodon](https://docs.joinmastodon.org/entities/Status/#Mention), +[Discord](https://discord.com/developers/docs/resources/channel#message-object), +and [Microsoft Teams](https://learn.microsoft.com/en-us/graph/api/resources/chatmessagemention?view=graph-rest-1.0). + +[^5]: Unfortunately some protocols do *not* provide structured data: the message +itself must be parsed for mentions, e.g. IRC or +[Slack](https://api.slack.com/reference/surfaces/formatting#mentioning-users). diff --git a/proposals/3970-device-scope-txnid.md b/proposals/3970-device-scope-txnid.md new file mode 100644 index 000000000..33c6024f6 --- /dev/null +++ b/proposals/3970-device-scope-txnid.md @@ -0,0 +1,227 @@ +# MSC3970: Scope transaction IDs to devices + +Transaction identifiers in the Client-Server API are currently scoped to the +concept of a "client session" which, when refresh tokens are used, can span a +sequence of access tokens. + +The spec [reads](https://spec.matrix.org/v1.6/client-server-api/#transaction-identifiers): + +> The scope of a transaction ID is a “client session”, where that session is +> identified by a particular access token. When refreshing an access token, the +> transaction ID’s scope is retained. This means that if a client with token `A` +> uses `TXN1` as their transaction ID, refreshes the token to `B`, and uses +> `TXN1` again it’ll be assumed to be a duplicate request and ignored. If the +> client logs out and back in between the `A` and `B` tokens, `TXN1` could be used +> once for each. + +The "client session" scope concept described can be complicated to implement. + +This MSC proposes that the scope of a transaction identifier is changed to something +that is easier to implement whilst maintaining required transaction semantics. + +The transaction IDs appear in two parts of the Client-Server API spec: + +1. As a identifier to allow the homeserver to make some `PUT` endpoints +[idempotent](https://spec.matrix.org/v1.6/client-server-api/#transaction-identifiers) +2. An unsigned field in the event data model for a client to tell if it sent an +event or not. a.k.a. solving the +["local echo"](https://spec.matrix.org/v1.6/client-server-api/#local-echo) problem + +For reference, the `PUT` endpoints that have the a `{txnId}` param are: + +- [`PUT /_matrix/client/v3/rooms/{roomId}/send/{eventType}/{txnId}`](https://spec.matrix.org/v1.6/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid) +- [`PUT /_matrix/client/v3/rooms/{roomId}/redact/{eventId}/{txnId}`](https://spec.matrix.org/v1.6/client-server-api/#put_matrixclientv3roomsroomidredacteventidtxnid) +- [`PUT /_matrix/client/v3/sendToDevice/{eventType}/{txnId}`](https://spec.matrix.org/v1.6/client-server-api/#put_matrixclientv3sendtodeviceeventtypetxnid) + +## Proposal + +It is proposed that the scope of transaction identifiers be changed from a +"client session" to a "device". + +A "device" is typically represented by a `device_id` elsewhere in the spec. + +For idempotency, this means the homeserver changing the method of identifying a +request from: + +- (`client session`, `HTTP path of request which includes the transaction ID`) + +to: + +- (`device_id`, `HTTP path of request which includes the transaction ID`) + +For local echo, the homeserver would now include the `transaction_id` in the +event data when it is serving a sync request from the same `device_id` as +determined from the access token. + +## Potential issues + +### This is technically a breaking change to the spec + +The main "issue" I see with this proposal is that this is technically a breaking +change to the spec. + +A device ID could have multiple sequences of access tokens associated +with it (since device ID can be specified as a parameter to +[`/login`](https://spec.matrix.org/v1.6/client-server-api/#post_matrixclientv3login)). + +For example, a "bot" implementation might masquerade as the same "device" despite +calling `/login` on every run by passing the same device ID. Such a device might +also use the same transaction ID on each run. + +Therefore it could potentially lead to a request being treated as a duplicate +where previously it would have been treated as a distinct request. + +However, some evidence has been collated which suggests that nothing would be impacted +in reality: + +#### 1. Data from the matrix.org homeserver suggests the change would have no impact + +The `matrix.org` homeserver is a reasonable size deployment and could be considered +reasonably representative of the diversity of Matrix clients. + +Synapse maintains a `event_txn_id` table +that contains a rolling 24 hour window of +(`user_id`, `token_id`, `room_id`, `txn_id`) tuples. + +Having analysed the contents of the table, it appears that there are no repeated +transaction IDs for a given user, token and room. + +n.b. not all `PUT` endpoints contribute to the table but I think the high-volume +ones do + +This suggests that the widening of the scope from token to device would not have caused +any issues during the periods sampled. + +For reference the following is the schema of the `event_txn_id` table: + +```sql + Table "matrix.event_txn_id" + Column | Type | Collation | Nullable | Default +-------------+--------+-----------+----------+--------- + event_id | text | | not null | + room_id | text | | not null | + user_id | text | | not null | + token_id | bigint | | not null | + txn_id | text | | not null | + inserted_ts | bigint | | not null | +Indexes: + "event_txn_id_token_id" btree (token_id) + "event_txn_id_event_id" UNIQUE, btree (event_id) + "event_txn_id_ts" btree (inserted_ts) + "event_txn_id_txn_id" UNIQUE, btree (room_id, user_id, token_id, txn_id) +Foreign-key constraints: + "event_txn_id_event_id_fkey" FOREIGN KEY (event_id) REFERENCES matrix.events(event_id) ON DELETE CASCADE + "event_txn_id_token_id_fkey" FOREIGN KEY (token_id) REFERENCES matrix.access_tokens(id) ON DELETE CASCADE +``` + +And the query to look for repeated transaction IDs: + +```sql +SELECT e1.txn_id, LEFT(e1.user_id, 5) AS user_id, e1.token_id, e2.token_id, e1.inserted_ts, e2.inserted_ts FROM matrix.event_txn_id e1, matrix.event_txn_id e2 WHERE e1.txn_id = e2.txn_id AND e1.event_id <> e2.event_id AND e1.event_id < e2.event_id AND e1.user_id = e2.user_id AND e1.room_id = e2.room_id ORDER BY e1.token_id; + txn_id | user_id | token_id | token_id | inserted_ts | inserted_ts +--------+---------+----------+----------+-------------+------------- +(0 rows) +``` + +#### 2. Conduit homeserver already scopes transaction IDs to devices + +As highlighted by the new Complement +[tests](https://github.com/matrix-org/complement/pull/613) the Conduit homeserver +is already scoping transaction IDs to devices. + +I can't find a related issue [listed](https://gitlab.com/famedly/conduit/-/issues), +so presumably this non-compliant behaviour isn't causing a known issue for +admins and users of the Conduit homeserver? + +### Is the "device" concept the right level of abstraction to use? + +One way to look at it is that device is already widely used in the end-to-end +encryption parts of the spec and so why isn't it suitable for this use case too? + +### What about two clients masquerading as a single device ID? + +I don't know if this actually works in practice. If this was a concern then it +could be mitigated by clarifying in the spec that if a client wishes to submit +requests using the same `device_id` as another client session that it should +choose transaction identifiers that are unique to that client session. + +## Alternatives + +### Do nothing + +We could leave the transaction ID scope as is. + +However, it makes it difficult to implement a features like +[MSC3861: Matrix architecture change to delegate authentication via OIDC](https://github.com/matrix-org/matrix-spec-proposals/pull/3861) +as the concept of a "client session" doesn't really exist in OIDC. + +As noted above, at least one homeserver implementation is also not implementing +the spec as it is today. + +It also turns out that the current implementation of refresh tokens in Synapse +breaks the transaction ID semantics already and needs to be +[fixed](https://github.com/matrix-org/synapse/issues/15141). + +### Make a backwards compatible change + +A backwards compatible alternative could be something like: + +1. For idempotency have clients opt-in to a new scope of transaction ID, but +support the current semantics too for compatibility +2. Have clients opt-in (e.g. request param on the sync endpoint) to receiving +transaction ID for all events in the sync response and make the client +responsible for identifying which messages they sent + +The disadvantage of this is that we create a load of extra maintenance work to +support both semantics for a period of time for (empirically) no gain in return. + +## Security considerations + +A malicious client can adopt an existing device ID of a user. This could +possibly allow some kind of denial of service attack. + +However, if such an attack where possible it would be possible to do so without +this MSC as device IDs are crucial to the implementation of end-to-end encryption. + +## Other recommendations + +I'm not suggesting that these recommendations are addressed in this proposal, but +more notes for future proposals or spec clarifications. + +### Clarification on idempotency semantics + +I have separately prepared a [spec PR](https://github.com/matrix-org/matrix-spec/pull/1449) +to clarify some of the idempotency semantics that doesn't modify the spec but is +useful to understand the context of this proposal. + +### Clarification on transaction ID time scope + +I also suggest that the spec be clarified over what time periods the transaction +ID is scoped for such that clients can be aware. This cloud simply be to say +that the time period is not defined and so may vary by implementation. + +### Recommend a less naive transaction ID format + +Currently the format of a transaction ID is not specified, but a recommendation +is [given](https://spec.matrix.org/v1.6/client-server-api/#transaction-identifiers): + +> After the [previous] request has finished, the {txnId} value should be changed +> (how is not specified; **a monotonically increasing integer is recommended**). + +I think this is an unnecessarily naive recommendation. + +In most clients environments a pseudo-random number generator will be available and so +could be used to generate a UUID/ULID or similar. + +As an aside, in my research I have found some clients use a "seconds since epoch" as a +transaction ID which introduces a limit on the maximum possible event transmission rate +per room to once per second. Perhaps a better recommendation could help prevent the +such behaviour being introduced in future. + +## Unstable prefix + +None needed. + +## Dependencies + +None. diff --git a/proposals/3987-push-actions-clean-up.md b/proposals/3987-push-actions-clean-up.md new file mode 100644 index 000000000..5857e21d7 --- /dev/null +++ b/proposals/3987-push-actions-clean-up.md @@ -0,0 +1,60 @@ +# MSC3987: Push actions clean-up + +There are [two defined push rule actions](https://spec.matrix.org/v1.6/client-server-api/#actions) +which are of dubious use: + +* `coalesce` is defined, but unused in the spec and not implemented on any homeservers [^1] + or clients [^2] beyond validating it or treating it identically to `notify`. +* `dont_notify` is a no-op, but frequently used as a way to denote do nothing. + It is clearer to provide an empty list of actions to avoid a situation where + additional actions are given with it, e.g. `["notify", "dont_notify"]` is + currently valid, but does not make sense. [^3] + +## Proposal + +The `coalesce` push rule action is removed from the Matrix specification. + +The `dont_notify` push rule action is deprecated. Homeservers and clients should +ignore it. Any [pre-defined rules](https://spec.matrix.org/v1.6/client-server-api/#actions) +which include the `dont_notify` action are redefined to have an empty list of actions: + +* `.m.rule.master` +* `.m.rule.suppress_notices` +* `.m.rule.member_event` + +It is recommended that homeservers continue accepting the `coalesce` and `dont_notify` +actions, but ignore them during processing. (Treating them as no-ops.) A future +Matrix spec version should remove them completely. + +## Potential issues + +A client might attempt to create a push rule with a `coalesce` or `dont_notify` +action that homeservers will reject as an unknown action. + +## Alternatives + +Do nothing and continue propagating confusion. + +## Security considerations + +None. + +## Dependencies + +None. + +[^1]: [Dendrite](https://github.com/search?q=repo%3Amatrix-org%2Fdendrite+CoalesceAction+NOT+path%3A%2F_test.go%24%2F&type=code), +[Synapse](https://github.com/search?q=repo%3Amatrix-org%2Fsynapse+coalesce+language%3ARust&type=code&l=Rust), +[Conduit](https://gitlab.com/search?search=coalesce&nav_source=navbar&project_id=22083768&group_id=4616224&search_code=true&repository_ref=next), +[Construct](https://github.com/matrix-construct/construct/blob/4ecf1ef037ecc1a5d1e3a1049d9a63cb0a6f3455/matrix/push.cc#L739-L740).. + +[^2]: [matrix-js-sdk](https://github.com/search?q=repo%3Amatrix-org/matrix-js-sdk%20Coalesce&type=code), +[matrix-ios-sdk](https://github.com/search?q=repo%3Amatrix-org%2Fmatrix-ios-sdk%20coalesce&type=code), +[matrix-rust-sdk](https://github.com/matrix-org/matrix-rust-sdk/commit/59edc22a35c4ef162ea0a8cafccdf25e37ab1070), +[matrix-android-sdk2](https://github.com/search?q=repo%3Amatrix-org/matrix-android-sdk2%20ACTION_COALESCE&type=code), +[Ruma](https://github.com/search?q=repo%3Aruma/ruma%20Coalesce&type=code). + +[^3]: It has been noted on recent MSCs that new rules should not use `dont_notify`, +see [MSC3786](https://github.com/matrix-org/matrix-spec-proposals/pull/3786#discussion_r864607531), +[MSC2153](https://github.com/matrix-org/matrix-spec-proposals/pull/2153#discussion_r450188777) / +[MSC2677](https://github.com/matrix-org/matrix-spec-proposals/pull/2677#discussion_r879701007).