19 KiB
Third-party invites
This module adds in support for inviting new members to a room where
their Matrix user ID is not known, instead addressing them by a third-party
identifier such as an email address. There are two flows here; one
if a Matrix user ID is known for the third-party identifier, and one if
not. Either way, the client calls /invite
with the details of the
third-party identifier.
The homeserver asks the identity server whether a Matrix user ID is known for that identifier:
- If it is, an invite is simply issued for that user.
- If it is not, the homeserver asks the identity server to record the details of the invitation, and to notify the invitee's homeserver of this pending invitation if it gets a binding for this identifier in the future. The identity server returns a token and public key to the inviting homeserver.
When the invitee's homeserver receives the notification of the binding,
it should insert an m.room.member
event into the room's graph for that
user, with content.membership
= invite
, as well as a
content.third_party_invite
property which contains proof that the
invitee does indeed own that third-party identifier. See the
m.room.member schema for more information.
Events
{{% event event="m.room.third_party_invite" %}}
Client behaviour
A client asks a server to invite a user by their third-party identifier.
{{% http-api spec="client-server" api="third_party_membership" anchor_base="thirdparty" %}}
Server behaviour
Upon receipt of an /invite
, the server is expected to look up the
third-party identifier with the provided identity server. If the lookup
yields a result for a Matrix User ID then the normal invite process can
be initiated. This process ends up looking like this:
+---------+ +-------------+ +-----------------+
| Client | | Homeserver | | IdentityServer |
+---------+ +-------------+ +-----------------+
| | |
| POST /invite | |
|------------------------------------>| |
| | |
| | GET /lookup |
| |--------------------------------------------------->|
| | |
| | User ID result |
| |<---------------------------------------------------|
| | |
| | Invite process for the discovered User ID |
| |------------------------------------------ |
| | | |
| |<----------------------------------------- |
| | |
| Complete the /invite request | |
|<------------------------------------| |
| | |
However, if the lookup does not yield a bound User ID, the homeserver
must store the invite on the identity server and emit a valid
m.room.third_party_invite
event to the room. This process ends up
looking like this:
+---------+ +-------------+ +-----------------+
| Client | | Homeserver | | IdentityServer |
+---------+ +-------------+ +-----------------+
| | |
| POST /invite | |
|------------------------------------>| |
| | |
| | GET /lookup |
| |-------------------------------------------------------------->|
| | |
| | "no users" result |
| |<--------------------------------------------------------------|
| | |
| | POST /store-invite |
| |-------------------------------------------------------------->|
| | |
| | Information needed for the m.room.third_party_invite |
| |<--------------------------------------------------------------|
| | |
| | Emit m.room.third_party_invite to the room |
| |------------------------------------------- |
| | | |
| |<------------------------------------------ |
| | |
| Complete the /invite request | |
|<------------------------------------| |
| | |
All homeservers MUST verify the signature in the event's
content.third_party_invite.signed
object.
The third-party user will then need to verify their identity, which
results in a call from the identity server to the homeserver that bound
the third-party identifier to a user. The homeserver then exchanges the
m.room.third_party_invite
event in the room for a complete
m.room.member
event for membership: invite
for the user that has
bound the third-party identifier.
If a homeserver is joining a room for the first time because of an
m.room.third_party_invite
, the server which is already participating
in the room (which is chosen as per the standard server-server
specification) MUST validate that the public key used for signing is
still valid, by checking key_validity_url
in the above described way.
No other homeservers may reject the joining of the room on the basis of
key_validity_url
, this is so that all homeservers have a consistent
view of the room. They may, however, indicate to their clients that a
member's membership is questionable.
For example, given H1, H2, and H3 as homeservers, UserA as a user of H1, and an identity server IS, the full sequence for a third-party invite would look like the following. This diagram assumes H1 and H2 are residents of the room while H3 is attempting to join.
+-------+ +-----------------+ +-----+ +-----+ +-----+ +-----+
| UserA | | ThirdPartyUser | | H1 | | H2 | | H3 | | IS |
+-------+ +-----------------+ +-----+ +-----+ +-----+ +-----+
| | | | | |
| POST /invite for ThirdPartyUser | | | |
|----------------------------------->| | | |
| | | | | |
| | | GET /lookup | | |
| | |---------------------------------------------------------------------------------------------->|
| | | | | |
| | | | Lookup results (empty object) |
| | |<----------------------------------------------------------------------------------------------|
| | | | | |
| | | POST /store-invite | | |
| | |---------------------------------------------------------------------------------------------->|
| | | | | |
| | | | Token, keys, etc for third-party invite |
| | |<----------------------------------------------------------------------------------------------|
| | | | | |
| | | (Federation) Emit m.room.third_party_invite | | |
| | |----------------------------------------------->| | |
| | | | | |
| Complete /invite request | | | |
|<-----------------------------------| | | |
| | | | | |
| | Verify identity | | | |
| |-------------------------------------------------------------------------------------------------------------------->|
| | | | | |
| | | | | POST /3pid/onbind |
| | | | |<---------------------------|
| | | | | |
| | | PUT /exchange_third_party_invite/:roomId | |
| | |<-----------------------------------------------------------------| |
| | | | | |
| | | Verify the request | | |
| | |------------------- | | |
| | | | | | |
| | |<------------------ | | |
| | | | | |
| | | (Federation) Emit m.room.member for invite | | |
| | |----------------------------------------------->| | |
| | | | | |
| | | | | |
| | | (Federation) Emit the m.room.member event sent to H2 | |
| | |----------------------------------------------------------------->| |
| | | | | |
| | | Complete /exchange_third_party_invite/:roomId request | |
| | |----------------------------------------------------------------->| |
| | | | | |
| | | | | Participate in the room |
| | | | |------------------------ |
| | | | | | |
| | | | |<----------------------- |
| | | | | |
Note that when H1 sends the m.room.member
event to H2 and H3 it does
not have to block on either server's receipt of the event. Likewise, H1
may complete the /exchange_third_party_invite/:roomId
request at the
same time as sending the m.room.member
event to H2 and H3.
Additionally, H3 may complete the /3pid/onbind
request it got from IS
at any time - the completion is not shown in the diagram.
H1 MUST verify the request from H3 to ensure the signed
property is
correct as well as the key_validity_url
as still being valid. This is
done by making a request to the identity server
/isvalid
endpoint, using the provided URL rather than constructing a new one. The
query string and response for the provided URL must match the Identity
Service Specification.
The reason that no other homeserver may reject the event based on
checking key_validity_url
is that we must ensure event acceptance is
deterministic. If some other participating server doesn't have a network
path to the keyserver, or if the keyserver were to go offline, or revoke
its keys, that other server would reject the event and cause the
participating servers' graphs to diverge. This relies on participating
servers trusting each other, but that trust is already implied by the
server-server protocol. Also, the public key signature verification must
still be performed, so the attack surface here is minimized.
Security considerations
There are a number of privacy and trust implications to this module.
It is important for user privacy that leaking the mapping between a matrix user ID and a third-party identifier is hard. In particular, being able to look up all third-party identifiers from a matrix user ID (and accordingly, being able to link each third-party identifier) should be avoided wherever possible. To this end, the third-party identifier is not put in any event, rather an opaque display name provided by the identity server is put into the events. Clients should not remember or display third-party identifiers from invites, other than for the use of the inviter themself.
Homeservers are not required to trust any particular identity server(s). It is generally a client's responsibility to decide which identity servers it trusts, not a homeserver's. Accordingly, this API takes identity servers as input from end users, and doesn't have any specific trusted set. It is possible some homeservers may want to supply defaults, or reject some identity servers for its users, but no homeserver is allowed to dictate which identity servers other homeservers' users trust.
There is some risk of denial of service attacks by flooding homeservers or identity servers with many requests, or much state to store. Defending against these is left to the implementer's discretion.