diff --git a/api/client-server/v1/membership.yaml b/api/client-server/v1/membership.yaml index c4d658fb..ff35fa35 100644 --- a/api/client-server/v1/membership.yaml +++ b/api/client-server/v1/membership.yaml @@ -159,26 +159,29 @@ paths: If the identity server does not know a Matrix user identifier for the passed third party identifier, the homeserver will issue an invitation which can be accepted upon providing proof of ownership of the third - party identifier. This is achieved by requesting a nonce and digest from - the identity server. When a user binds the invited third party - identifier to a Matrix user ID, the identity server will give the user a - list of pending invitations, each containing: + party identifier. This is achieved by the identity server generating a + token, which it gives to the inviting homeserver. The homeserver will + add an ``m.room.third_party_invite`` event into the graph for the room, + containing that token. + + When a user binds the invited third party identifier to a Matrix user ID, + the identity server will give the user a list of pending invitations, + each containing: - The room ID to which they were invited - - The digest given to the homeserver + - The token given to the homeserver - - A secret which, when appended to the nonce, digests to the above digest, - i.e. digest = sha256(nonce + secret) + - A signature of the token, signed with the identity server's private key - The digest algorithm to be used is SHA256. + - The matrix user ID who invited them to the room If the identity server did know the Matrix user identifier for the third party identifier, the home server will append a ``m.room.member`` event to the room. - If a digest and nonce are requested from the identity server, the home - server will append a ``m.room.token_based_invite`` event to the room. + If a token is requested from the identity server, the home server will + append a ``m.room.third_party_invite`` event to the room. security: - accessToken: [] parameters: @@ -195,13 +198,13 @@ paths: type: object example: |- { - "identity_server": "matrix.org", + "id_server": "matrix.org", "medium": "email", "address": "cheeky@monkey.com", "display_name": "A very cheeky monkey" } properties: - identity_server: + id_server: type: string description: The hostname+port of the identity server which should be used for third party identifier lookups. medium: @@ -213,7 +216,7 @@ paths: display_name: type: string description: A user-friendly string describing who has been invited. It should not contain the address of the invitee, to avoid leaking mappings between third party identities and matrix user IDs. - required: ["identity_server", "medium", "address", "display_name"] + required: ["id_server", "medium", "address", "display_name"] responses: 200: description: The user has been invited to join the room. diff --git a/event-schemas/examples/v1/m.room.member b/event-schemas/examples/v1/m.room.member index b9cd2671..279eb484 100644 --- a/event-schemas/examples/v1/m.room.member +++ b/event-schemas/examples/v1/m.room.member @@ -3,7 +3,12 @@ "content": { "membership": "join", "avatar_url": "mxc://localhost/SEsfnsuifSDFSSEF#auto", - "displayname": "Alice Margatroid" + "displayname": "Alice Margatroid", + "token": "pc98", + "public_key": "abc123", + "key_validity_url": "https://magic.forest/verifykey", + "signature": "q1w2e3", + "sender": "@zun:zun.soft" }, "state_key": "@alice:localhost", "origin_server_ts": 1431961217939, diff --git a/event-schemas/examples/v1/m.room.third_party_invite b/event-schemas/examples/v1/m.room.third_party_invite new file mode 100644 index 00000000..82ac5248 --- /dev/null +++ b/event-schemas/examples/v1/m.room.third_party_invite @@ -0,0 +1,14 @@ +{ + "age": 242352, + "content": { + "display_name": "Alice Margatroid", + "key_validity_url": "https://magic.forest/verifykey", + "public_key": "abc123" + }, + "state_key": "pc98", + "origin_server_ts": 1431961217939, + "event_id": "$WLGTSEFSEF:localhost", + "type": "m.room.third_party_invite", + "room_id": "!Cuyf34gef24t:localhost", + "sender": "@example:localhost" +} diff --git a/event-schemas/schema/v1/m.room.member b/event-schemas/schema/v1/m.room.member index c0fb103c..a3be9a1d 100644 --- a/event-schemas/schema/v1/m.room.member +++ b/event-schemas/schema/v1/m.room.member @@ -1,7 +1,7 @@ { "type": "object", "title": "The current membership state of a user in the room.", - "description": "Adjusts the membership state for a user in a room. It is preferable to use the membership APIs (``/rooms//invite`` etc) when performing membership actions rather than adjusting the state directly as there are a restricted set of valid transformations. For example, user A cannot force user B to join a room, and trying to force this state change directly will fail.", + "description": "Adjusts the membership state for a user in a room. It is preferable to use the membership APIs (``/rooms//invite`` etc) when performing membership actions rather than adjusting the state directly as there are a restricted set of valid transformations. For example, user A cannot force user B to join a room, and trying to force this state change directly will fail. The token, public_key, key_validity_url, signature, and sender properties either must all or none be set - they are set if the invite was an ``m.room.third_party_invite`` event, and absent if the invite was an ``m.room.member`` event.", "allOf": [{ "$ref": "core-event-schema/state_event.json" }], @@ -21,7 +21,27 @@ "displayname": { "type": ["string", "null"], "description": "The display name for this user, if any. This is added by the homeserver." - } + }, + "token": { + "type": "string", + "description": "A token which must be correctly signed, in order to join the room." + }, + "key_validity_url": { + "type": "string", + "description": "A URL which can be fetched, with querystring public_key=public_key, to validate whether the key has been revoked. The URL must return a JSON object containing a boolean property named 'valid'." + }, + "public_key": { + "type": "string", + "description": "A base64-encoded ed25519 key with which token must be signed." + }, + "signature": { + "type": "string", + "description": "A base64-encoded signature of token with public_key." + }, + "sender": { + "type": "string", + "description": "The matrix user ID of the user who send the invite which is being used." + } }, "required": ["membership"] }, diff --git a/event-schemas/schema/v1/m.room.third_party_invite b/event-schemas/schema/v1/m.room.third_party_invite new file mode 100644 index 00000000..eae8c015 --- /dev/null +++ b/event-schemas/schema/v1/m.room.third_party_invite @@ -0,0 +1,37 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "title": "An invitation to a room issued to a third party identifier, rather than a matrix user ID.", + "description": "Acts as an ``m.room.member`` invite event, where there isn't a target user_id to invite. This event contains a token and a public key whose private key must be used to sign the token. Any user who can present that signature may use this invitation to join the target room.", + "allOf": [{ + "$ref": "core#/definitions/state_event" + }], + "properties": { + "content": { + "type": "object", + "properties": { + "display_name": { + "type": "string", + "description": "A user-readable string which represents the user who has been invited. This should not contain the user's third party ID, as otherwise when the invite is accepted it would leak the association between the matrix ID and the third party ID." + }, + "key_validity_url": { + "type": "string", + "description": "A URL which can be fetched, with querystring public_key=public_key, to validate whether the key has been revoked. The URL must return a JSON object containing a boolean property named 'valid'." + }, + "public_key": { + "type": "string", + "description": "A base64-encoded ed25519 key with which token must be signed." + } + }, + "required": ["display_name", "key_validity_url", "public_key"] + }, + "state_key": { + "type": "string", + "description": "The token, of which a signature must be produced in order to join the room." + }, + "type": { + "type": "string", + "enum": ["m.room.third_party_invite"] + } + } +} diff --git a/specification/modules/third_party_invites.rst b/specification/modules/third_party_invites.rst new file mode 100644 index 00000000..ae0d74e8 --- /dev/null +++ b/specification/modules/third_party_invites.rst @@ -0,0 +1,39 @@ +Third party invites +=================== + +.. _module: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. + +Events +------ + +{{m_room_third_party_invite_event}} + +Client behaviour +---------------- + +A client asks a server to invite a user by their third party identifier. + +See the documentation for /invite for more information. + +Server behaviour +---------------- + +All homeservers MUST verify that sign(``token``, ``public_key``) = ``signature``. + +If a client of the current homeserver is joining by an +``m.room.third_party_invite``, that homesever MUST validate that the public +key used for signing is still valid, by checking ``key_validity_url``. + +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 MUST validate that the public key used for signing is still valid, by +checking ``key_validity_url``. + +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. + diff --git a/specification/targets.yaml b/specification/targets.yaml index d77bf8b5..4b404ff1 100644 --- a/specification/targets.yaml +++ b/specification/targets.yaml @@ -22,6 +22,7 @@ groups: # reusable blobs of files when prefixed with 'group:' - modules/content_repo.rst - modules/end_to_end_encryption.rst - modules/history_visibility.rst + - modules/third_party_invites.rst - modules/push_overview.rst # relative depth - { 1: [modules/push_cs_api.rst , modules/push_push_gw_api.rst] }