723 lines
39 KiB
Markdown
723 lines
39 KiB
Markdown
# Canonical DMs (server-side middle ground edition)
|
|
|
|
In the messaging space it is common for apps to expose exactly 1 chat for a conversation
|
|
between two users, and in some cases a specific set of users. Matrix currently does not
|
|
adequately support this as it allows for multiple rooms to represent a DM between two users.
|
|
Although supporting multiple chats with individuals is potentially useful for some use cases,
|
|
clients are increasingly wanting to be able to support exactly one DM for a user.
|
|
|
|
Any messaging app is likely to want to differentiate DMs from all the noise, and Riot in
|
|
particular is looking at using the DM between users for a more personal experience within
|
|
the app (such as key verification between users). This proposal focuses on immutable DMs
|
|
between users to assist in identifying the "One True DM" between users and prevent the
|
|
room's scope from increasing.
|
|
|
|
For comparison to Matrix, the following chat apps/platforms all support and encourage a
|
|
single DM between users. They all redirect the user to an existing chat with the target
|
|
user, creating a new DM if needed.
|
|
|
|
* Slack
|
|
* Discord
|
|
* IRC
|
|
* Telegram
|
|
* Gitter
|
|
* Facebook Messenger
|
|
* Twitter
|
|
* Hangouts
|
|
* Instagram
|
|
* Rocket.Chat
|
|
* Steam
|
|
* WhatsApp
|
|
* SMS
|
|
* and probably many others...
|
|
|
|
Platforms which allow/encourage users to have multiple DMs with a specific person are:
|
|
|
|
* Matrix (currently)
|
|
* Email
|
|
* XMPP (technically speaking)
|
|
|
|
This proposal covers how servers enforce exactly one DM (ideally immutable) between a set
|
|
of users. Clients have some responsibilities to render this appropriately, however the server
|
|
is expected to do most of the heavy lifting here. Immutable DMs are direct chats between
|
|
users where the participants can't change the room's aesthetics. For example, users can't
|
|
change the name or topic of the room, and they'll always be DMs.
|
|
|
|
This proposal supports "direct chats" or "DMs" in the sense of private conversations with
|
|
a set of users. This means that a DM between 3 users is possible, however the common case
|
|
is likely to be 1:1 (2 person) chats.
|
|
|
|
Immutability of DMs is also proposed in this MSC, however as an optional feature of DMs and
|
|
not a requirement.
|
|
|
|
|
|
## Proposal
|
|
|
|
In short, the entire direct chat module in Matrix is deprecated by this proposal. The direct
|
|
chat module relies on clients behaving themselves and tracking all the information for themselves
|
|
while somehow coming to the same conclusions as any other client the user happens to run. In
|
|
practice this becomes very difficult to do and maintain, particularly given the direct chats
|
|
are stored in an account data blob. If two clients were to update the account data at the same
|
|
time (which happens *a lot*), the clients would end up overwriting each other.
|
|
|
|
Direct chats consist of "important" and "unimportant" users to allow for bots and personal
|
|
assistants in a chat. Unimportant users don't affect the DM-ness of the room and are essentially
|
|
observers on the room (with speaking rights). How classification is done is defined later in
|
|
this proposal.
|
|
|
|
Direct chats are now expected to be identified and labelled by the server through the `summary`
|
|
response field on `/sync`. The `m.heroes` MUST contain the important users in a DM, regardless
|
|
of the presence of a name, canonical alias, etc on the room. The heroes must never be truncated
|
|
for a DM, and must not contain the syncing user's ID. A new field, `m.kind`, is mandatory for
|
|
direct chats which can only be `m.dm`. Future values may be added in a future proposal. Like
|
|
the other fields in the summary, `m.kind` may be omitted if there was no change in the field.
|
|
|
|
For the purposes of demoting a DM from its DM status, `m.kind` can also be the literal `null`
|
|
to identify it as "unknown or irrelevant kind".
|
|
|
|
Implementation note: clients can identify whether a room is a 1:1 or multi-way chat by looking
|
|
at the `m.kind` and `m.heroes`: if the kind is `m.dm` and there's only 1 user in `m.heroes`
|
|
then the room is a 1:1 chat. If the kind is `m.dm` and there's 2+ users in the `m.heroes` then
|
|
it is a multi-way/private group chat.
|
|
|
|
Implementations should also be wary of semantics regarding what causes a room to jump between
|
|
a multi-way DM and a 1:1 DM. Users can be invited (and joined) to either kind of room without
|
|
causing the room to jump to a different kind: for example, a 1:1 chat can have several unimportant
|
|
users in it (bots, etc) which do not make it a multi-way DM. Similarly, if an important user
|
|
were to leave a multi-way DM the room does not become a 1:1 DM. This is all described in further
|
|
detail later in this proposal.
|
|
|
|
With irrelevant fields not included, an example sync response would be:
|
|
```json
|
|
{
|
|
"rooms": {
|
|
"join": {
|
|
"!a:example.org": {
|
|
"summary": {
|
|
"m.heroes": ["@alice:example.org"],
|
|
"m.joined_member_count": 1,
|
|
"m.invited_member_count": 1,
|
|
"m.kind": "m.dm"
|
|
}
|
|
},
|
|
"!b:example.org": {
|
|
"summary": {
|
|
"m.heroes": ["@bob:example.org"],
|
|
"m.joined_member_count": 2,
|
|
"m.invited_member_count": 0,
|
|
"m.kind": "m.dm"
|
|
}
|
|
},
|
|
"!c:example.org": {
|
|
"summary": {
|
|
"m.heroes": ["@alice:example.org", "@bob:example.org"],
|
|
"m.joined_member_count": 3,
|
|
"m.invited_member_count": 0,
|
|
"m.kind": "m.dm"
|
|
}
|
|
},
|
|
"!d:example.org": {
|
|
"summary": {
|
|
"m.joined_member_count": 5,
|
|
"m.invited_member_count": 2,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Clients rendering the above should end up with something like the following:
|
|
|
|
![alice-bob-over-banquet](./images/2199-dms-client.png)
|
|
|
|
|
|
Servers are expected to be able to identify direct chats for users and flag them appropriately
|
|
in the room summary. How existing DMs are migrated is covered later in this proposal. The
|
|
server must use these rules to determine if a room is a direct chat:
|
|
* The join rules are `invite`.
|
|
* The rejoin rules are `invite` or `join` (as per [MSC2213](https://github.com/matrix-org/matrix-doc/pull/2213)).
|
|
* The user's most recent membership event has `"m.dm": true` in the `content`.
|
|
* The room has an explicit power level event (running a DM without a PL event is not supported).
|
|
* The room has at least 2 possible important users (who may or may not be joined yet).
|
|
* The user themselves is important in the room.
|
|
* The room is not tombstoned.
|
|
* The room is not soft-tombstoned by the server (described later in this proposal).
|
|
* No important users have been banned. Kicking or leaving the room does not affect the DM-ness
|
|
of the room.
|
|
|
|
Assuming no other conflicts arise (duplicate chats, etc) the room is considered a DM between
|
|
the important users in the room. Important users are identified simply by having a power level
|
|
greater than or equal to the `state_default` power level requirement.
|
|
|
|
The server MUST maintain the `m.dm` flag when updating or transitioning any user's membership
|
|
event. The server SHOULD refuse to let the user manipulate the flag directly through `PUT /state`
|
|
and similar APIs (such as inviting users to a room). Servers MUST consider a value of `false`
|
|
the same as the field not being present.
|
|
|
|
Servers are not expected to identify a room as a direct chat until the server resides in the
|
|
room. Servers MUST populate the room summary for invites if the server is a resident of the
|
|
room. Clients SHOULD use the room summary on invites and joined rooms to identify if a room
|
|
is a direct chat or not. Where the summary is not available and an `m.dm` field is on the
|
|
invite event, clients should be skeptical but otherwise trusting of the room's DM status.
|
|
For example, a skeptical client might list the room with a warning stating that the room
|
|
is flagged as a DM without being appropriately decorated, but not prevent the user from
|
|
accepting the invite.
|
|
|
|
Servers MUST attempt to identify (or re-affirm) rooms as a DM whenever the relevant state
|
|
events in the rules above change or are updated.
|
|
|
|
Soft tombstoned rooms are rooms which are considered tombstoned without an actual tombstone
|
|
state event being present. This is typically used to flag rooms where tombstone events cannot
|
|
be sent as tombstoned. This proposal goes into more detail on this a bit later.
|
|
|
|
#### A note about leaving DMs
|
|
|
|
Unless an important user is banned, the DM is still considered alive and should be able to
|
|
be rejoined even if some important people were to leave. However, there's a good chance that
|
|
when all important users leave that the room becomes unjoinable and therefore dead. To help
|
|
combat this, servers should approach rooms where important users have left with caution by
|
|
assuming the room cannot be brought back to life.
|
|
|
|
If [MSC1777](https://github.com/matrix-org/matrix-doc/pull/1777) or similar were to land,
|
|
the server should abuse the capability to determine how alive the room is. By refreshing
|
|
its perspective, it can avoid scenarios where it has to make assumptions about the state
|
|
of the room it is no longer in. The server should never autojoin the user to the room,
|
|
regardless of MSC1777 support or not.
|
|
|
|
The following cases are reinforcement reading for the conditions mentioned so far. They
|
|
describe how the server is expected to behave given a common scenario - not all scenarios
|
|
are covered here.
|
|
|
|
**Case 1: The DM resides on a single homeserver**: when all important users leave the DM,
|
|
the DM is to be considered soft-tombstoned for those users. This will cause a new DM to be
|
|
created. The presence of unimportant users can be enough for a homeserver to consider the
|
|
room not dead and therefore rejoinable.
|
|
|
|
**Case 2: The DM resides on 2+ homeservers**: this gets a bit tricky. When the last important
|
|
user leaves, that homeserver would not have visibility on the room anymore but does not have
|
|
enough information for it to be tombstoned (soft or otherwise). Another server can still rejoin
|
|
the room due to unimportant users being left behind, or keep the room alive without the other
|
|
homeserver continuing participation. The homeservers involved will still converge on the room
|
|
when other conversations start.
|
|
|
|
**Case 3: A duplicate DM comes in for a room the server has left**: when the server does not
|
|
have visibility on the members of a room anymore it cannot tombstone newly created rooms and
|
|
point to the room it can't see. If the server happens to be a resident of the room, it can
|
|
absolutely tombstone the new room in favour of the old room, even if the server's membership
|
|
is just unimportant users and the important users having left. When the server does encounter
|
|
an invite for a DM which duplicates a room it has left, it must assume that its perspective
|
|
of the older room is antiquated and that something happened to cause a new invite to come
|
|
in. After joining, if a server happened to tombstone the room to point to the older room
|
|
then the user could then try and join the room and refresh the server's perspective.
|
|
|
|
*Note*: Originally case 3 had a step for the server to autojoin the user into the existing
|
|
room to refresh its state, however this left a glaring hole for abuse to filter its way down
|
|
to a given user.
|
|
|
|
|
|
#### Creating DMs
|
|
|
|
The room creation preset `trusted_private_chat` is deprecated and to be removed in a future
|
|
specification version. Clients should stop using that preset and instead use the new endpoint
|
|
for creating DMs, as defined here.
|
|
|
|
**`POST /_matrix/client/r0/create_dm`**:
|
|
|
|
This is mostly a clone of `/createRoom` with the unnecessary parts left behind. This does not
|
|
replace `/createRoom`. This requires authentication and can be rate-limited.
|
|
|
|
Request parameters (JSON body):
|
|
* `room_version` (`string`) - The version to create the room with. Same as `room_version` from
|
|
`/createRoom`.
|
|
* `invite` (`[string]`) - The list of user IDs to invite to the room. Same as `invite` from
|
|
`/createRoom` with the added behaviour described below.
|
|
* `invite_3pid` (`[3pid invite]`) - The list of 3rd party users to invite to the room. Same as
|
|
`invite_3pid` from `/createRoom` with the added behaviour described below.
|
|
* `creation_content` (`object`) - Additional content for the creation event. This is also the
|
|
same as `creation_content` from `/createRoom`.
|
|
|
|
Note: `name`, `topic`, `visibility`, `room_alias_name`, `initial_state`, `preset`, `is_direct`,
|
|
and `power_level_content_override` are all intentionally excluded from the clone. There is no
|
|
preset support, aesthetic characteristics of the room (name, topic, etc) are not supported for
|
|
immutable DMs under this MSC, and the remaining fields do not have strong use cases for DMs -
|
|
clients can always send state event updates after creating the room, for instance.
|
|
|
|
Response (JSON):
|
|
The server creates the room similar to how `/createRoom` operates:
|
|
|
|
1. An `m.room.create` event is created, as is `m.room.power_levels` using the defaults.
|
|
2. Join rules of `invite` are set with a rejoin rule of `join`, as per
|
|
[MSC2213](https://github.com/matrix-org/matrix-doc/pull/2213)). This is to allow DMs to be
|
|
re-used (described later) without sacrificing security of the DM by letting random people
|
|
who were invited at some point into the room.
|
|
3. History visibility of `invited` is set.
|
|
4. Guest access of `can_join` is set so that guests can be included in DMs where needed.
|
|
5. Encryption is enabled by default using the most preferred megolm algorithm in the spec.
|
|
Currently this would be `m.megolm.v1.aes-sha2`.
|
|
6. Power levels with the following non-default structure are set:
|
|
|
|
```json
|
|
{
|
|
"events_default": 0,
|
|
"state_default": 50,
|
|
"users_default": 0,
|
|
"ban": 50,
|
|
"invite": 50,
|
|
"kick": 50,
|
|
"redact": 50,
|
|
"notifications": {
|
|
"room": 50
|
|
},
|
|
"events": {
|
|
"m.room.name": 100,
|
|
"m.room.avatar": 100,
|
|
"m.room.topic": 100,
|
|
"m.room.history_visibility": 100,
|
|
"m.room.power_levels": 100,
|
|
"m.room.join_rules": 100,
|
|
"m.room.encryption": 100,
|
|
"m.room.canonical_alias": 100
|
|
},
|
|
"users": {
|
|
<see below>
|
|
},
|
|
"third_party_users": {
|
|
<see below>
|
|
}
|
|
}
|
|
```
|
|
|
|
This power level event is not applied until after the invites have been sent as otherwise
|
|
it would be impossible to give third party users power. Servers should apply a default
|
|
power level event to the room and then apply the power level event described here after
|
|
the invites have been sent. Servers can optimize this process and use this power level
|
|
event at step 0 of the room creation process if there are no third party invites to send.
|
|
Users invited to the room get power level 50, including the creator.
|
|
* Third party users invited to the room get power level 50, as described by MSC2212 (see
|
|
later on in this proposal for how this works). Like the invited users, all third party
|
|
users invited as a result of the `/create_dm` call are considered important.
|
|
7. Users (3rd party and otherwise) are invited. Important users (those invited and the creator)
|
|
MUST have `"m.dm": true` in their membership event content. Third party important users get
|
|
the same `m.dm` flag on their `m.room.third_party_invite` event contents.
|
|
|
|
Servers MUST return an existing room ID if the server already knows about a DM between the
|
|
important users. The important users which have left the DM MUST be explicitly re-invited.
|
|
If the user trying to create the room is not in the DM themselves, the server MUST try and
|
|
re-join the user to the room. If the re-join fails, the server should create a new room
|
|
for the DM and consider the existing one as soft-tombstoned.
|
|
|
|
The rationale for preventing either party from manipulating the room is to ensure that there is
|
|
equal representation of the room from both parties (automatically named, automatic avatar, etc).
|
|
Users generally expect that a DM will be predictably dressed for searching and scanning, meaning
|
|
that the other parties cannot change the aesthetics of the room. For predictable history and
|
|
privacy, the room's history & join rules are locked out from the users. The users can upgrade the
|
|
room at any time to escape power level struggles, although this may not maintain the DM status.
|
|
|
|
*Note*: The `private_chat` preset is untouched as it does not affect DMs. `trusted_private_chat`
|
|
was intended for DMs despite its name and is deprecated as a result.
|
|
|
|
*Note*: Servers are still expected to be able to identify DMs from `/createRoom`, and clients
|
|
are able to craft DMs using `/createRoom`. When this happens, servers MUST return an existing
|
|
room ID for a DM if one exists and it looks like the user is trying to create a DM through the
|
|
`/createRoom` endpoint.
|
|
|
|
#### Glare and duplicated rooms
|
|
|
|
There is a potential for two users to start a DM with each other at the same time, preventing
|
|
either server from short-circuiting the `/createRoom` call. Servers MUST NOT prevent users
|
|
from accepting or rejecting invites to known conflicting DM rooms. Servers SHOULD NOT retract
|
|
the invite and SHOULD NOT automatically join the user to the room. The rationale is that the
|
|
user is likely to have already received a notification about a room invite and would be
|
|
confused if the room went missing or was automatically accepted. Clients are welcome to adjust
|
|
their UX as desired to do something else instead (like automatically reject/accept the invite
|
|
or hide it when it is an apparent duplicate).
|
|
|
|
When the server joins a room it has identified as conflicting/duplicating an existing DM it
|
|
MUST apply the conflict resolution algorithm:
|
|
1. Order all the conflicting room's creation events by their `origin_server_ts`.
|
|
2. Pick the room with the **oldest** creation event as the canonical DM.
|
|
3. Send a tombstone state event to the non-canonical DM rooms pointing at the canonical room.
|
|
* If the tombstone cannot be sent by the server due to auth rules, it must consider the
|
|
room as "soft-tombstoned". A soft-tombstoned room is just a flag for preventing a room
|
|
from being counted as a DM and has no further behaviour or implication in this proposal.
|
|
|
|
The rationale for picking the oldest creation event is it is likely the room with the most
|
|
context for the user. It is possible that a malicious user intentionally creates a new room
|
|
with an ancient creation event however there's very little gain in doing so. The algorithm
|
|
is chosen such that any two servers come to the same conclusion on which room to use.
|
|
|
|
|
|
#### Upgrading DM rooms
|
|
|
|
There may come a time where DM rooms need to be upgraded. When DM rooms are upgraded through the
|
|
`/upgrade` endpoint, servers MUST preserve the `content` of the previous room's creation event and
|
|
otherwise apply the `immutable_dm` preset over the new room. If conflicts arise between
|
|
the previous room's state and the state applied by the preset, the preset takes preference over
|
|
the previous room's state.
|
|
|
|
Servers MUST create the new room by using the the room creation rules listed earlier in this
|
|
proposal. This means inviting the other parties of the previous room to the new room automatically
|
|
and ensuring the room state is as described above. Auth errors with transferring state (such as
|
|
the room name) must be considered non-fatal to the upgrade process: just skip that state event.
|
|
|
|
Note: normally room upgrades modify the power levels in the old room in an attempt to mute all
|
|
participants. However, no one will have power to modify the power levels in the old room.
|
|
Therefore, servers MUST NOT fail to upgrade a room due to being unable to update the power levels
|
|
in the old room. This is considered acceptable by this proposal because upgraded DM rooms will
|
|
lose their DM status, making them at worst just another room for the user.
|
|
|
|
Servers should do their best to follow tombstones to new DM rooms, however they should not assume
|
|
that those new rooms are valid DM rooms nor should they automatically join/invite the important
|
|
users to the room.
|
|
|
|
|
|
#### Detecting server support
|
|
|
|
Servers which implement this MSC prior to it appearing in a spec release MUST advertise as such
|
|
through the unstable feature flag `m.immutable_dms` on `/versions`. Clients which detect server
|
|
support should not only check for the feature flag but also the presence of a supported spec
|
|
version on the server (as the flag may disappear once the feature lands in a release). Currently
|
|
this proposal is expected to land in r0.6.0 of the Client-Server API.
|
|
|
|
Some servers have thousands and even millions of users which will need their DMs migrated. In
|
|
order to allow those servers to advertise the feature (both as an unstable flag and as a released
|
|
spec version) the client SHOULD assume that the server has not migrated its user's DMs until the
|
|
server sends the `m.direct_merged` account data event. More information about migration can be
|
|
found in the next section.
|
|
|
|
|
|
#### Migration
|
|
|
|
Users already have DMs which need to be correctly mapped by the server. Servers MUST use the
|
|
following process for migration for each user:
|
|
1. Grab a list of room IDs from their `m.direct` account data (if present). Ignore the user IDs
|
|
that are associated with the room IDs.
|
|
2. Identify each room as either a DM or some other room, flagging them as appropriate, using the
|
|
following steps:
|
|
|
|
* If the room does not have a `rejoin_rule`, consider the rejoin rule as `join` for the
|
|
purposes of identification.
|
|
* Identify the room using the rules specified earlier in this proposal.
|
|
* If the room did not have a `rejoin_rule`, attempt to set the rejoin rule to `join`. If that
|
|
fails, do not consider the room a DM. Note that this will not cause the rule to magically
|
|
work on prior room versions: it is just set to ensure that the DM flagging conditions
|
|
are met as they don't care for the behaviour, just the value being present.
|
|
|
|
Identification of DMs may involve conflict resolution, which should only happen after the steps
|
|
above have been executed.
|
|
3. Set `m.direct_merged` account data with `content` consisting of a single array, `rooms`, listing
|
|
all the room IDs the server iterated over, regardless of result.
|
|
|
|
The server SHOULD support automatically migrating any additions to `m.direct` even after migrating
|
|
the user. This is to allow for the user having an older client which does not yet support proper
|
|
DMs. Removals from `m.direct` are recommended to be left unhandled to ensure consistency with the
|
|
updated DMs.
|
|
|
|
Clients SHOULD NOT assume that the server will migrate direct chats if the user's `m.direct`
|
|
account data does not list any rooms. This is more important for new accounts created after the
|
|
DMs feature has been introduced in Matrix: if the server had to set `m.direct_merged` for every
|
|
user in the future, the server would be collecting largely useless data. Instead, the server is
|
|
given the option to skip migrations for users that have no data to migrate (such as new users).
|
|
|
|
|
|
#### Sugar APIs (for appservices/thin bots)
|
|
|
|
Appservice users and some bots are unlikely to see the room summary information. In an attempt to
|
|
combat this, some APIs are provided for users and thin clients to call and poke into the server's
|
|
view of their DM list.
|
|
|
|
The expected use cases for not using `/sync` are:
|
|
* When appservices want to contact a user in a DM fashion (IRC bridge sending NickServ info).
|
|
* When thin bots aren't syncing and want to alert users (monitoring bots triggered by external event).
|
|
* Clients which don't trust their local cache of DMs.
|
|
|
|
Appservices in particular are expected to heavily use the DM-finding API below and should cache
|
|
the result. When the appservice sees an event which the server might have reidentified the room
|
|
for it should invalidate its cache. Servers should make both of these endpoints highly cached.
|
|
|
|
**`GET /_matrix/client/r0/user/:userId/dms`**
|
|
|
|
The only parameter, `:userId`, is the user ID requesting their DMs. The auth provided must be valid
|
|
for this user. Example response:
|
|
```json
|
|
{
|
|
"dms": {
|
|
"!a:example.org": {
|
|
"important": ["@alice:example.org"]
|
|
},
|
|
"!b:example.org": {
|
|
"important": ["@bob:example.org"]
|
|
},
|
|
"!c:example.org": {
|
|
"important": ["@alice:example.org", "@bob:example.org"]
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
The `important` array does not include the user themselves.
|
|
|
|
**`GET /_matrix/client/r0/user/:userId/dm?involves=@alice:example.org&involves=@bob:example.org`**
|
|
|
|
The `:userId` is the user ID requesting their DMs - the auth provided must be valid for this user.
|
|
This additionally takes `?involves` (specified multiple times) to search for a DM involving the given
|
|
users. It should be considered a bad request if the user asks for a DM involving themselves.
|
|
|
|
Example response:
|
|
```json
|
|
{
|
|
"room_id": "!c:example.org"
|
|
}
|
|
```
|
|
|
|
If no DM exists involving those users, an empty object should be returned with 200 OK.
|
|
|
|
|
|
#### Third party invites
|
|
|
|
Third party invites (email invites) are hard to handle as the participants in the room are
|
|
unlikely to be able to modify the power levels in the room because the immutable DM preset
|
|
forbids this by design. To remedy this, this proposal requires that third party users get
|
|
their own power level, as described in [MSC2212](https://github.com/matrix-org/matrix-doc/pull/2212).
|
|
|
|
In addition to MSC2212, this proposal requires that a `m.dm` field be added to the
|
|
`m.room.third_party_invite` event. When the invite is claimed, the `m.dm` field must
|
|
be copied to the generated membership event.
|
|
|
|
#### Complete list of deprecations
|
|
|
|
Earlier in this proposal it was mentioned that the existing direct chats module is replaced
|
|
by this proposal, however it didn't exactly explain the scope. The existing module is
|
|
optionally supported by servers (which requires very little effort on their part), however
|
|
this proposal recommends that servers drop support completely per the deprecations below.
|
|
|
|
Deprecated things:
|
|
* `is_direct` on `/createRoom` now does nothing (ignored field, like any other unrecognized
|
|
field).
|
|
* `is_direct` on membership events now means nothing (replaced by `m.dm`).
|
|
* `m.direct` account data is deprecated (replaced with the behaviour described in this
|
|
proposal).
|
|
|
|
After sufficient time has passed, the deprecated components should be removed from the
|
|
specification.
|
|
|
|
|
|
#### Test cases for the server/proposal to consider
|
|
|
|
**Disclaimer**: This section might add clarity but might also be confusing. Sorry.
|
|
|
|
**Simple case**
|
|
|
|
The happy path of DMs.
|
|
|
|
1. Alice starts a DM with Bob. Alice and Bob end up with a single room.
|
|
2. Bob then starts a DM with Alice and gets redirected to the existing room by their client
|
|
or server.
|
|
3. Alice tries to start a new DM with Bob. Like Bob in the last step, Alice is redirected
|
|
to the existing room.
|
|
4. Bob then starts a DM with Alice and Charlie. A new room is created. Similar redirection
|
|
semantics take place when the three parties attempt to duplicate the room.
|
|
|
|
**Simple glare**
|
|
|
|
The slightly less happy path of DMs: when servers collide.
|
|
|
|
1. Alice and Bob reside on different homeservers.
|
|
2. Alice starts a DM with Bob, who does not accept nor reject it.
|
|
3. Bob starts a DM with Alice, who does not accept not reject it.
|
|
4. Alice accepts Bob's invite. Alice's server realizes this is a duplicate room and sends
|
|
a tombstone to the room, pointing at the DM Alice created earlier.
|
|
* During this, Bob's server would have originally flagged the room as a DM for Bob and
|
|
Alice, but the tombstone signals to Bob's homeserver that something is up and assumes
|
|
that Bob has no DM with Alice (yet).
|
|
5. Bob gets a tombstone in Alice's DM and realizes there's a pending invite. Bob accepts.
|
|
6. Bob's homeserver sees that the room is a DM and flags it as the DM for Bob and Alice.
|
|
|
|
Alice and Bob's homeserver have come to the same conclusion. In the worst case, both servers
|
|
could accept the invites at the same time and conflict with each other on the tombstone. The
|
|
worst case for this is a split DAG on the tombstone with two tombstone state events, resolved
|
|
through state resolution. Both servers would have still come to the same conclusion on which
|
|
room to use so either of the tombstone events sent could be considered a no-op.
|
|
|
|
**De-duplication during migration / tombstone tree**
|
|
|
|
1. Alice has 3 existing DMs with Bob, who resides in both rooms.
|
|
2. The server updates (Alice and Bob happen to be on the same server in this example, however
|
|
the scenario works cross-server too).
|
|
3. The server realizes that Alice and Bob share multiple DMs and pick the oldest DM. The
|
|
server tombstones the other 2 rooms.
|
|
4. Alice and Bob now have exactly one DM with each other.
|
|
|
|
The room map looks a bit complicated now, but there's a tree of rooms where two point to
|
|
a single room. Clients aren't exactly expected to represent this nicely but can try if they
|
|
want to. There will be no `predecessor` for them to work off of.
|
|
|
|
If the canonical DM room was then upgraded or otherwise tombstoned itself, the tree starts
|
|
to become more linear.
|
|
|
|
**Tombstone tree with glare**
|
|
|
|
Following the example of a "tombstone tree" then creating a conflicting room with glare
|
|
the servers should resolve to use the canonical room identified by the tombstone tree. The
|
|
conflicting room should be tombstoned to point at the canonical room.
|
|
|
|
**Change in power levels, join rules, or membership**
|
|
|
|
If the power levels or join rules change then the server needs to re-evaluate the room. If
|
|
the room ends up being no longer a DM, the server must flag it as such to the relevant clients
|
|
and *not* send a tombstone into the room. The parties are considered to not have a DM.
|
|
|
|
If the membership changes such that someone ends up leaving the room (on their own or through
|
|
a kick/ban) then the DM is no longer considered a DM, similar to the paragraph above.
|
|
|
|
If the room starts to look like a DM again (members rejoin, join rules become acceptable again,
|
|
etc) then the conflict resolution algorithm might take place depending on the user's DMs.
|
|
|
|
**Malicious takeover of a DM**
|
|
|
|
1. Alice and Bob are on different homeservers, but don't have to be for this scenario.
|
|
2. Alice starts a DM with Bob. Bob accepts.
|
|
3. Bob's homeserver sends a tombstone into the DM to point it at Matrix HQ.
|
|
4. Both Alice and Bob no longer have a viable DM: both homeservers do not consider Matrix HQ
|
|
as the replacement room, and do not treat the tombstoned room as a DM.
|
|
|
|
Ultimtely this is a wasted effort: both homeservers would start a new DM after the room was
|
|
directed at HQ.
|
|
|
|
**The cases covered by leaving DMs above**
|
|
|
|
This proposal covers a few cases that servers are supposed to consider when a user (or many
|
|
users) leaves a room. Please reference that section for more detail.
|
|
|
|
|
|
## Tradeoffs / issues
|
|
|
|
This is really complicated for servers to implement. The alternative is that the client
|
|
does all the work (see below in 'Alternative solutions') however this is deemed unreasonable
|
|
by the author and early reviewers of the proposal.
|
|
|
|
This allows for bots and personal assistants to be residents of a room. There is an argument
|
|
for restricting DMs to just important users (excluding any chance of unimportant users),
|
|
namely to ensure that security is not sacrificed through leaky bots/assistants. The author
|
|
believes there is a stronger argument for assistant-style/audit bots in a DM, such as a reminder
|
|
bot or Giphy bot. The rationale being that although bots pose a security risk to the conversation,
|
|
the rooms are supposed to have a history visibility setting which prevents the history from
|
|
being exposed to the new parties. Important parties additionally should have the power to remove
|
|
the new user account from the room if they don't agree.
|
|
|
|
This proposal enables encryption by default for DMs. Encryption in the specification is
|
|
moderately incomplete as of writing, making the move to enable encryption by default somewhat
|
|
questionable. For instance, a large number of bots and bridges in the ecosystem do not currently
|
|
support encryption and generally fall silent in encrypted rooms. Bots in today's ecosystem
|
|
can use [Pantalaimon](https://github.com/matrix-org/pantalaimon) or add dedicated support
|
|
if they so choose, however bridges are still unable to decrypt messages even with Pantalaimon.
|
|
Bridges being able to support encryption (they'll need EDUs and a device ID) is a problem
|
|
for a different MSC, and likely a different spec version. In the meantime, this proposal
|
|
specifically does not prohibit DMs which are unencrypted and bridges can start DMs as such,
|
|
or refuse to participate in DMs which are encrypted. Generally speaking, bridges in the
|
|
current ecosystem start the DMs with users instead of the users contacting the bridge,
|
|
however there are some prominent examples of this being reversed (IRC, most puppet bridges,
|
|
etc). Clients can combat this by identifying bridge namespaces (also a future spec problem)
|
|
and offering users the option to not encrypt the room, or by just having that checkbox
|
|
always present. Ideally, this proposal and cross-signing, better encryption for bridges,
|
|
etc all land concurrently in the next specification version.
|
|
|
|
This proposal is vague about which encryption algorithm to support. This is an intentional
|
|
choice by the author to support the possibility of a given encryption algorithm being
|
|
deemed unsuitable by Matrix in the future. For example, if a given algorithm was broken or
|
|
found to have security flaws it would be difficult to update the specification if the preset
|
|
had baked in a specific algorithm. Instead, the proposal asks the specification to make
|
|
a recommendation and for servers to think critically of the available algorithms (of which
|
|
only one is actually available at the moment) to make the best decision for their users.
|
|
|
|
|
|
## Alternative solutions
|
|
|
|
The sugar APIs are a bit awkward for the value they provide. Bridges are more commonly in
|
|
need of direct chats and thin bots arguably can still sync their way through life. Bridges,
|
|
and possibly clients, could receive changes to their DMs over a purpose-built room managed
|
|
by the server. Similar to server notices, the server could maintain state events in a readonly
|
|
immutable room for the user/client to sit in and receive updates. The author feels as though
|
|
this is more complicated than it should be, requiring servers to maintain hundreds, thousands,
|
|
or even millions of dedicated rooms for users which can easily overwhelm an appservice. For
|
|
instance, the Freenode IRC bridge has millions of users which would equate to millions of
|
|
rooms which all need to be synced down the appservice pipe which is already fairly congested.
|
|
|
|
Servers could be less involved and all this work could be done by the client. Although
|
|
easier for servers, clients are more likely to run into conflicts between themselves. The
|
|
most common type of conflicts for clients to have is updating account data at the same time,
|
|
which happens a lot more often than it should. When there's conflicts in updating account
|
|
data, there's a strong possibility that data is lost due to overwrites. Sure, the client
|
|
could be told to use room tagging instead however a similar problem occurs when one client
|
|
wants to add a tag and another wants to remove it. An earlier draft of this proposal did
|
|
cover what a mostly client-side implementation of immutable DMs could look like: it can
|
|
be found [here](https://gist.github.com/turt2live/6a4e21437508bce76be89d6cbaf65723).
|
|
|
|
Servers could be even more involved and require a whole new room version to change the
|
|
event auth rules for DMs. The author strongly believes that this is far too complicated
|
|
and way overkill for what Matrix needs/wants to achieve, despite having written an earlier
|
|
draft of this proposal calling for exactly that. Many of the problems with this approach
|
|
are described in that draft, found [here](https://gist.github.com/turt2live/ed0247531d07c666b19dd95f7471eff4).
|
|
|
|
There is also an argument that the existing solution is fine and clients should just
|
|
make it work for their purposes. The author believes this is an invalid argument given
|
|
the introduction of this proposal and the problems highlighted regarding client-driven
|
|
DMs.
|
|
|
|
|
|
## Security considerations
|
|
|
|
Some clients are investigating the usage of immutable DMs for security and privacy related
|
|
functionality of their design. For example, client designers are looking into handling key
|
|
verification and cross-signing within the immutable DM (if such a concept existed) rather than
|
|
in a floating dialog. To accomplish this in a safe and secure way it may be desirable to have
|
|
high levels of enforcement regarding immutable DMs. This may increase the user's comfort in
|
|
knowing that none of the users in the room can manipulate it. Clients which need this level of
|
|
confidence may wish to ignore insecure DMs and attempt to start new ones by upgrading the
|
|
existing DM through a predefined `preset` (ideally acquiring permission first).
|
|
|
|
There is a theoretical scenario where a homeserver or user could maliciously prevent a user
|
|
from opening a new DM with them. This is considered a feature given modules like ignoring users
|
|
exists, however a homeserver/user could continously set up a scenario where an existing DM
|
|
becomes unjoinable while sending tombstones for all new DM rooms which point to the unjoinable
|
|
room. This has a largely social impact on the room that technology cannot resolve (if people
|
|
are going to be mean, they're going to be mean). Attempts to alter the DM such that the user
|
|
cannot join without being notified are possible (changing the rejoin rules) however in those
|
|
cases both homeservers should be considering the room as no longer a DM, unless of course
|
|
the homeserver was being the malicious actor. Because of how tombstones currently work in
|
|
Matrix, users would have to perform an action to try and join the new DM and eventually the
|
|
user may get frustrated with the other user and ignore them, breaking the cycle of new DMs
|
|
being created.
|
|
|
|
|
|
## Pitfalls with the current system
|
|
|
|
The existing DM system in Matrix support multiple DMs with a user, however that system is
|
|
not necsarily more complex than this proposal (in fact, it's probably simpler than what
|
|
is proposed here). The existing system has the disadvantage of being non-deterministic where
|
|
two users in the same room can have a different view of that room in their clients: Alice
|
|
can see it as a DM whereas Bob sees it as just another room. Having non-deterministic DMs
|
|
can lead to conversational confusion ("hey Bob, let's go to our DM" and Bob asking where that
|
|
is) or uncertainty about where to send sensitive information.
|
|
|
|
Using the example of clients wanting to use DMs as a channel for exchanging key information,
|
|
knowing that all parties in the conversation perceive it as a DM is important. If the
|
|
verification were to land in a random room, the other party may miss it or be concerned it is
|
|
not a real verification request.
|
|
|
|
Further, the existing system is managed completely client-side and every client is expected
|
|
to clone the same logic. By pushing the DM calculations to the server, all the client has to
|
|
do is render them. Most messenger Matrix clients want or have a concept of DMs, and instead of
|
|
duplicating the same code over and over the server could just inform the client of its DMs.
|
|
|
|
|
|
## Conclusion
|
|
|
|
Immutable DMs are a hard problem to solve and involve a bunch of tiny changes to multiple parts
|
|
of the specification. Of all the iterations of this proposal, this proposal is targeted at being
|
|
a safe balance of client complexity and user safety, sacrificing the server's complexity in favour
|
|
of predictable results. Most clients are interested in implementing DMs in some capacity, and this
|
|
proposal helps make it less painful and more reliable for them. Users should feel safer to use
|
|
DMs with this proposal given the enabled encryption and permissions models imposed on the room.
|