@ -6,11 +6,12 @@ user to indicate that they want to join a room.
# Proposal
# Proposal
This proposal implements the reserved "knock" membership type for the
This proposal implements the reserved "knock" membership type for the
`m.room.member` state event. This state event indicates that when a user
`m.room.member` state event. This state event indicates that when a user
knocks on a room, they are asking for permission to join. It contains an
knocks on a room, they are asking for permission to join. Like all membership
optional "reason" parameter to specify the reason you want to join. Like
events, it contains an optional "reason" parameter to specify the reason you
other membership types, the parameters "displayname" and "avatar_url" are
want to join. Like other membership types, the parameters "displayname" and
optional. This membership can be set from users who aren't currently in said
"avatar_url" are optional. This membership can be set from users who aren't
room. An example for the membership would look like the following:
currently in said room. An example for the membership would look like the
following:
```json
```json
{
{
"membership": "knock",
"membership": "knock",
@ -33,25 +34,27 @@ being "leave" can knock on a room. This means that a user that is banned, has
already knocked or is currently in the room cannot knock on it.
already knocked or is currently in the room cannot knock on it.
### Join Rules
### Join Rules
This proposal introduces a new possible value for `join_rule` in
This proposal makes use of the existing "knock" join rule. The value of
` m.room.join_rules`: "knock". The value of `join_rule` in `m.room.join_rules`
` join_rule` in the content of the `m.room.join_rules` state event for a room
must be set to "knock" for a knock to succeed. This means that existing rooms
must be set to "knock" for a knock to succeed. This means that existing rooms
will need to opt into allowing knocks in their rooms. Other than allowing
will need to opt into allowing knocks in their rooms. Other than allowing
knocks, "knock" is no different from the "invite" join rule.
knocks, a join rule of "knock" is functionally equivalent to that of
"invite", except that it additionally allows external users to change their
membership to "knock" under certain conditions.
As the join rules are modified, the auth rules are as well. The [current auth
As the join rules are modified, the auth rules are as well. The [current auth
rules](https://matrix.org/docs/spec/rooms/v1#authorization-rule s) are defined
rules](https://matrix.org/docs/spec/rooms/v6#authorization-rules-for-event s) are defined
by each room version. To change these rules, the implementation of this
by each room version. To change these rules, the implementation of this
proposal must be done in a new room version. The explicit changes to the auth
proposal must be done in a new room version. The explicit changes to the auth
rules from room version 5 are:
rules from room version 5 are:
* Under "5. If type is `m.room.member` ", the following should be added:
* Under "5. If type is `m.room.member` ", the following will be added:
```
```
a. If `membership` is `knock` :
a. If `membership` is `knock` :
i. If the `join_rule` is anything other than `knock` , reject.
i. If the `join_rule` is anything other than `knock` , reject.
ii. If `sender` does not match `state_key` , reject.
ii. If `sender` does not match `state_key` , reject.
iii. If the `sender` 's membership is not `ban` or `join` , allow.
iii. If the `sender` 's membership is not `ban` , `knock` or `join` , allow.
iv. Otherwise, reject.
iv. Otherwise, reject.
```
```
@ -108,29 +111,27 @@ related to the knock attempt.
### Membership change to `leave`
### Membership change to `leave`
The knock has been rejected by someone in the room.
The knock has been rejected by someone in the room, or the knocking user has
rescinded their knock.
XXX: There is also an open question here about who should be able to reject a
To rescind a knock, the knocking user's client must call [`POST
knock. When revoking an invite for a user, perhaps counter-intuitively, you
/_matrix/client/r0/rooms/{roomId}/leave`](https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-rooms-roomid-leave).
need to have a high enough power level to kick users, rather than invite
them. You also need to have a higher power level than them. Should the same
be done for knocking, assuming the knocking user has the default power level?
Or should it be the same power level that's required to accept the knock?
To reject a knock, the client should call [`POST
To reject a knock, the rejecting user's client must call [`POST
/_matrix/client/r0/rooms/{roomId}/kick`](https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-rooms-roomid-kick)
/_matrix/client/r0/rooms/{roomId}/kick`](https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-rooms-roomid-kick)
with the user ID of the knocking user in the JSON body.
with the user ID of the knocking user in the JSON body. Rejecting a knock
over federation has a slight catch, however.
At this point, if the knocking user is on another homeserver, then the
homeserver of the rejecting user needs to send the `leave` event over
When the knocking user is on another homeserver, the homeserver of the
federation to the knocking homeserver. However, this is a bit tricky as it is
rejecting user needs to send the `leave` event over federation to the
currently very difficult to have events from a room propagate over federation
knocking homeserver. However, this is a bit tricky as it is currently very
if the receiving homeserver is not in the room. This is due to the remote
difficult to have events from a room propagate over federation when the
homeserver being unable to verify that the event being sent is actually from
receiving homeserver is not in the room. This is due to the remote homeserver
a homeserver in the room - and that the homeserver in the room had the
being unable to verify that the event being sent is actually from a
required power level to send it. This is a problem that currently affects
homeserver in the room - and that the homeserver in the room had the required
other similar operations, such as disinviting or unbanning a federated user.
power level to send it. This is a problem that currently affects other,
In both cases, they won't be notified as their homeserver is not in the room.
similar operations, such as disinviting or unbanning a federated user. In
both cases, they won't be notified as their homeserver is not in the room.
While we could send easily send the leave event as part of a generic
While we could send easily send the leave event as part of a generic
transaction to the remote homeserver, that homeserver would have no way to
transaction to the remote homeserver, that homeserver would have no way to
@ -147,9 +148,9 @@ knock will be on the same homeserver you knocked through. Perhaps the
homeserver you knocked through could listen for this and then send the event
homeserver you knocked through could listen for this and then send the event
back to you - but what if it goes offline in the meantime?
back to you - but what if it goes offline in the meantime?
As such, this feature working over federation is de-scoped for now, and left
As such, informing remote homeservers about the rejection of knocks over
to a future MSC which can solve this problem across the board for all
federation is de-scoped for now, and left to a future MSC which can solve
affected features in a suitable way. Rejections should still work for the
this class of problem in a suitable way. Rejections should still work for the
homeservers that are in the room, as they can validate the leave event for
homeservers that are in the room, as they can validate the leave event for
they have access to the events it references.
they have access to the events it references.
@ -164,7 +165,8 @@ in the room bans the user. This will have the same effect as rejecting the
knock, and in addition prevent any further knocks by this user from being
knock, and in addition prevent any further knocks by this user from being
allowed into the room.
allowed into the room.
If the user is unbanned, then knocks will be accepted again.
If the user is unbanned, they will be able to send a new knock which could be
accepted.
To ban the user, the client should call [`POST
To ban the user, the client should call [`POST
/_matrix/client/r0/rooms/{roomId}/ban`](https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-rooms-roomid-ban) with the user ID of the knocking user in the JSON body.
/_matrix/client/r0/rooms/{roomId}/ban`](https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-rooms-roomid-ban) with the user ID of the knocking user in the JSON body.
@ -174,22 +176,26 @@ knock.
## Client-Server API
## Client-Server API
Two new endpoints are introduced in the Client-Server API (similarly to
A new endpoint is introduced in the Client-Server API: `POST
join): `POST /_matrix/client/r0/rooms/{roomId}/knock` and `POST
/_matrix/client/r0/knock/{roomIdOrAlias}`. This allows the client to state
/_matrix/client/r0/knock/{roomIdOrAlias}`. These allow the client to state
their intent to knock on a room.
their intent to knock on a room.
Additionally, extensions to the `GET /_matrix/client/r0/sync` endpoint are
Additionally, extensions to the `GET /_matrix/client/r0/sync` endpoint are
introduced. These allow a client to receive information about the status of
introduced. These allow a client to receive information about the status of
their knock attempt.
their knock attempt.
### `POST /_matrix/client/r0/rooms/{roomId}/knock`
The newly proposed endpoint requires authentication and can be rate limited.
The path parameter (`roomId`) is the room on which you want to knock. It is
required. The post body accepts an optional string parameter, `reason` , which
is the reason you want to join the room. A request could look as follows:
### `POST /_matrix/client/r0/knock/{roomIdOrAlias}`
The path parameter (`roomIdOrAlias`) is either the room ID or the alias of
the room you want to knock on. Additionally several `server_name` parameters
can be specified via the query parameters. The post body accepts an optional
string parameter, `reason` , which is the reason you want to join the room. A
request could look as follows:
```json
```json
POST /_matrix/client/r0/rooms/%21d41d8cd%3Amatrix.org/knock HTTP/1.1
POST /_matrix/client/r0/knock/%23foxes%3Amatrix.org?server_name=matrix.org& server_name=elsewhere.ca HTTP/1.1
Content-Type: application/json
Content-Type: application/json
{
{
@ -204,15 +210,6 @@ The user knocked successfully. Example reply:
{}
{}
```
```
##### Status code 400:
This request was invalid, e.g. bad JSON. Example reply:
```json
{
"errcode": "M_UNKNOWN",
"error": "An unknown error occurred"
}
```
##### Status code 403:
##### Status code 403:
The user wasn't allowed to knock (e.g. they are banned). Error reply:
The user wasn't allowed to knock (e.g. they are banned). Error reply:
```json
```json
@ -222,36 +219,6 @@ The user wasn't allowed to knock (e.g. they are banned). Error reply:
}
}
```
```
##### Status code 429:
This request was rate-limited. Example reply:
```json
{
"errcode": "M_LIMIT_EXCEEDED",
"error": "Too many requests",
"retry_after_ms": 2000
}
```
### `POST /_matrix/client/r0/knock/{roomIdOrAlias}`
The path parameter (`roomIdOrAlias`) is either the room ID or the alias of
the room you want to knock on. Additionally several `server_name` parameters
can be specified via the query parameters. The post body accepts an optional
string parameter, `reason` , which is the reason you want to join the room. A
request could look as follows:
```json
POST /_matrix/client/r0/knock/%23foxes%3Amatrix.org?server_name=matrix.org& server_name=elsewhere.ca HTTP/1.1
Content-Type: application/json
{
"reason": "I want to join this room as I really love foxes!"
}
```
#### Responses:
The possible responses are the same as for the `POST
/_matrix/client/r0/rooms/{roomId}/knock` endpoint.
### Extensions to `GET /_matrix/client/r0/sync`
### Extensions to `GET /_matrix/client/r0/sync`
In [the response to
In [the response to
@ -267,17 +234,26 @@ a list of `StrippedStateEvent`. `StrippedStateEvent`s are defined as state
events that only contain the `sender` , `type` , `state_key` and `content`
events that only contain the `sender` , `type` , `state_key` and `content`
keys.
keys.
Note that while `join` and `leave` keys in `/sync` use `state` , we use
`knock_state` here. This mirrors `invite` s use of `invite_state` .
These stripped state events contain information about the room, most notably
These stripped state events contain information about the room, most notably
the room's name and avatar. A client will need this information to show a
the room's name and avatar. A client will need this information to show a
nice representation of pending knocked rooms. The recommended events to
nice representation of pending knocked rooms. The recommended events to
include are the join rules, canonical alias, avatar, and name of the room,
include are the join rules, canonical alias, avatar, name and encryption
rather than all room state. This behaviour matches the information sent to
state of the room, rather than all room state. This behaviour matches the
remote homeservers when invited their users to a room.
information sent to remote homeservers when invited their users to a room.
This prevents unneeded state from the room leaking out, and also speeds
This prevents unneeded state from the room leaking out, and also speeds
things up (think not sending over hundreds of membership events from big
things up (think not sending over hundreds of membership events from big
rooms).
rooms).
Also note that like `invite_state` , state events from `knock_state` are
purely for giving the user some information about the current state of the
room that they have knocked on. If the user was previously in the room, the
state events in `knock_state` are not intended to overwrite any historical
state. This applies storage of state on both the homeserver and the client.
The following is an example of knock state coming down `/sync` .
The following is an example of knock state coming down `/sync` .
Request:
Request:
@ -395,10 +371,17 @@ This request was invalid, e.g. bad JSON. Example reply:
}
}
```
```
### `PUT /_matrix/federation/v 1 /send_knock/{roomId}/{eventId}`
### `PUT /_matrix/federation/v 2 /send_knock/{roomId}/{eventId}`
Submits a signed knock event to the resident homeserver for it to accept into
Submits a signed knock event to the resident homeserver for it to accept into
the room's graph. Note that event format may differ between room versions.
the room's graph. Note that event format may differ between room versions.
While this is a new endpoint, we start off at `v2` to align with the rest of
the `/v2/send_*` endpoints. The switch from `v1` to `v2` occurred as part of
[MSC1802 ](https://github.com/matrix-org/matrix-doc/pull/1802 ) and required
that `send_*` endpoints no longer return a redundant HTTP error code in
response bodies. As we do the same here, and for consistency's sake, for
`send_knock` will begin at endpoint `v2` as well.
Request format:
Request format:
| Parameter | Type | Description |
| Parameter | Type | Description |
@ -417,7 +400,7 @@ Response Format:
A request could look as follows:
A request could look as follows:
```json
```json
PUT /_matrix/federation/v1 /send_knock/%21abc123%3Amatrix.org/%24abc123%3Aexample.org HTTP/1.1
PUT /_matrix/federation/v2 /send_knock/%21abc123%3Amatrix.org/%24abc123%3Aexample.org HTTP/1.1
Content-Type: application/json
Content-Type: application/json
{
{
@ -492,8 +475,8 @@ request content.
# Potential issues
# Potential issues
This new feature would allow users to send events into rooms that they don't
This new feature would allow users to send events into rooms that they don't
partake in. That is why this proposal adds a new join rule, in order to allow
partake in. That is why this proposal enables the a `knock` join rule, in
room admins to opt in to this behaviour.
order to allow room admins to opt in to this behaviour.
# Alternatives
# Alternatives
The two endpoints for the Client-Server API seem redundant, this MSC followed
The two endpoints for the Client-Server API seem redundant, this MSC followed
@ -525,7 +508,10 @@ Another abuse vector is allowed by the ability for users to rescind knocks.
This is to help users in case they knocked on a room accidentally, or simply
This is to help users in case they knocked on a room accidentally, or simply
no longer want to join a room they've knocked on. While this is a useful
no longer want to join a room they've knocked on. While this is a useful
feature, it also allows users to spam a room by knocking and rescinding their
feature, it also allows users to spam a room by knocking and rescinding their
knocks over and over.
knocks over and over. Particularly note-worthy is that this will generate
state events that homeserver in the room will need to process. And while
join/leave state changes will do the same in a public room, the act of
knocking is much lighter than the act of joining a room.
In both cases, room admins should employ typical abuse mitigation tools, such
In both cases, room admins should employ typical abuse mitigation tools, such
as user bans and server ACLs. Clients are encouraged to ease employing these
as user bans and server ACLs. Clients are encouraged to ease employing these
@ -533,10 +519,10 @@ tools easy even if the offensive user or server is present not in the room.
# Unstable prefix
# Unstable prefix
An unstable feature flag is added to the `unstable_features` dict of
An unstable feature flag is not required due to this proposal's requirement
`/_matrix/client/versions` with the key `xyz.amorgan.knock` and value `true` .
of a new room version. Clients can check for a room version that includes
If this key is present, this is a signal to clients that the homeserver ha s
knocking via the Client-Server API's [capabilitie s
experimental support for room knocking .
endpoint](https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-capabilities) .
The new endpoints should contain an unstable prefix during experimental
The new endpoints should contain an unstable prefix during experimental
implementation. The unstable counterpart for each endpoint is:
implementation. The unstable counterpart for each endpoint is:
@ -558,10 +544,10 @@ S-S make_knock:
S-S send_knock:
S-S send_knock:
* `PUT /_matrix/federation/v 1 /send_knock/{roomId}/{eventId}`
* `PUT /_matrix/federation/v 2 /send_knock/{roomId}/{eventId}`
* `PUT /_matrix/federation/unstable/xyz.amorgan/send_knock/{roomId}/{eventId}`
* `PUT /_matrix/federation/unstable/xyz.amorgan/send_knock/{roomId}/{eventId}`
And finally, an unstable prefix is added to the key that comes down `/sync` ,
And finally, an unstable prefix is added to the key that comes down `/sync` ,
the new join rule for rooms and the `content.membership` key of the member
the join rule for rooms and the `content.membership` key of the member
event sent into rooms when a user has knocked successfully. Instead of
event sent into rooms when a user has knocked successfully. Instead of
`knock` , experimental implementations should use `xyz.amorgan.knock` .
`knock` , experimental implementations should use `xyz.amorgan.knock` .