diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 25a2fb68..cdaf0431 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,6 +2,7 @@ name: "Spec" env: HUGO_VERSION: 0.139.0 + PYTHON_VERSION: 3.13 on: push: @@ -40,7 +41,7 @@ jobs: - name: "➕ Setup Python" uses: actions/setup-python@v5 with: - python-version: '3.9' + python-version: ${{ env.PYTHON_VERSION }} cache: 'pip' cache-dependency-path: scripts/requirements.txt - name: "➕ Install dependencies" @@ -59,7 +60,7 @@ jobs: - name: "➕ Setup Python" uses: actions/setup-python@v5 with: - python-version: '3.9' + python-version: ${{ env.PYTHON_VERSION }} cache: 'pip' cache-dependency-path: scripts/requirements.txt - name: "➕ Install dependencies" @@ -78,7 +79,7 @@ jobs: - name: "➕ Setup Python" uses: actions/setup-python@v5 with: - python-version: '3.9' + python-version: ${{ env.PYTHON_VERSION }} cache: 'pip' cache-dependency-path: scripts/requirements.txt - name: "➕ Install dependencies" @@ -120,7 +121,7 @@ jobs: - name: "➕ Setup Python" uses: actions/setup-python@v5 with: - python-version: '3.9' + python-version: ${{ env.PYTHON_VERSION }} cache: 'pip' cache-dependency-path: scripts/requirements.txt - name: "➕ Install dependencies" @@ -154,6 +155,11 @@ jobs: --api server-server \ -r "$RELEASE" \ -o spec/server-server-api/api.json + scripts/dump-openapi.py \ + --base-url "https://spec.matrix.org${{ needs.calculate-baseurl.outputs.baseURL }}" \ + --api identity \ + -r "$RELEASE" \ + -o spec/identity-service-api/api.json tar -czf openapi.tar.gz spec - name: "📤 Artifact upload" uses: actions/upload-artifact@v4 @@ -172,7 +178,7 @@ jobs: - name: "➕ Setup Python" uses: actions/setup-python@v5 with: - python-version: '3.9' + python-version: ${{ env.PYTHON_VERSION }} - name: "➕ Install towncrier" run: "pip install 'towncrier'" - name: "Generate changelog" @@ -283,10 +289,11 @@ jobs: npm i npm run get-proposals - name: "⚙️ hugo" + env: + HUGO_PARAMS_VERSION_STATUS: "historical" # Create a baseURL like `/v1.2` out of the `v1.2` tag run: | - echo -e '[params.version]\nstatus="historical"' > historical.toml - hugo --config config.toml,historical.toml --baseURL "/${GITHUB_REF/refs\/tags\//}" -d "spec" + hugo --baseURL "/${GITHUB_REF/refs\/tags\//}" -d "spec" - name: "📥 Spec definition download" uses: actions/download-artifact@v4 diff --git a/assets/scss/_styles_project.scss b/assets/scss/_styles_project.scss index 9b020715..772b8412 100644 --- a/assets/scss/_styles_project.scss +++ b/assets/scss/_styles_project.scss @@ -316,13 +316,19 @@ Custom SCSS for the Matrix spec h2 { font-weight: $font-weight-bold; font-size: 1.3rem; - margin: 3rem 0 .5rem 0; + margin: 1.5rem 0 1rem 0; } h3 { font-weight: $font-weight-bold; font-size: 1.1rem; - margin: 1.5rem 0 .75rem 0; + margin: 1.5rem 0 1rem 0; + + } + + /* Reduce top margin of h3 if previous sibling is a h2 */ + h2 + h3 { + margin-top: 1rem; } hr { @@ -367,11 +373,6 @@ Custom SCSS for the Matrix spec } } - // add some space between two tables when they are right next to each other - & + table { - margin-top: 4rem; - } - caption { caption-side: top; color: $dark; @@ -443,6 +444,17 @@ Custom SCSS for the Matrix spec } } + /* Have consistent spacing around tables and examples */ + table, .highlight { + margin-top: 0; + margin-bottom: 2rem; + + /* We don't need the margin on the last child of the .rendered-data block */ + &:last-child { + margin-bottom: 0; + } + } + pre { border: 0; border-left: solid 5px $secondary; diff --git a/changelogs/application_service/newsfragments/2182.clarification b/changelogs/application_service/newsfragments/2182.clarification new file mode 100644 index 00000000..82426693 --- /dev/null +++ b/changelogs/application_service/newsfragments/2182.clarification @@ -0,0 +1 @@ +Minor fixes to JSON schemas. diff --git a/changelogs/client_server/newsfragments/2167.clarification b/changelogs/client_server/newsfragments/2167.clarification new file mode 100644 index 00000000..99805f73 --- /dev/null +++ b/changelogs/client_server/newsfragments/2167.clarification @@ -0,0 +1 @@ +Clarify that `format` is required if `formatted_body` is specified. diff --git a/changelogs/client_server/newsfragments/2169.clarification b/changelogs/client_server/newsfragments/2169.clarification new file mode 100644 index 00000000..49648401 --- /dev/null +++ b/changelogs/client_server/newsfragments/2169.clarification @@ -0,0 +1 @@ +The `latest_event` in an aggregated set of thread events uses the same format as the event itself. diff --git a/changelogs/client_server/newsfragments/2121.clarification b/changelogs/client_server/newsfragments/2171.clarification similarity index 100% rename from changelogs/client_server/newsfragments/2121.clarification rename to changelogs/client_server/newsfragments/2171.clarification diff --git a/changelogs/client_server/newsfragments/2175.feature b/changelogs/client_server/newsfragments/2175.feature new file mode 100644 index 00000000..ddd71d27 --- /dev/null +++ b/changelogs/client_server/newsfragments/2175.feature @@ -0,0 +1 @@ +Add `format` query parameter to `GET /state/{eventType}/{stateKey}` to allow fetching metadata of a specific state event. diff --git a/changelogs/server_server/newsfragments/2128.clarification b/changelogs/client_server/newsfragments/2177.clarification similarity index 100% rename from changelogs/server_server/newsfragments/2128.clarification rename to changelogs/client_server/newsfragments/2177.clarification diff --git a/changelogs/client_server/newsfragments/2179.clarification b/changelogs/client_server/newsfragments/2179.clarification new file mode 100644 index 00000000..3ccb2333 --- /dev/null +++ b/changelogs/client_server/newsfragments/2179.clarification @@ -0,0 +1 @@ +Fix various typos throughout the specification. diff --git a/changelogs/client_server/newsfragments/2182.clarification b/changelogs/client_server/newsfragments/2182.clarification new file mode 100644 index 00000000..82426693 --- /dev/null +++ b/changelogs/client_server/newsfragments/2182.clarification @@ -0,0 +1 @@ +Minor fixes to JSON schemas. diff --git a/changelogs/client_server/newsfragments/2187.feature b/changelogs/client_server/newsfragments/2187.feature new file mode 100644 index 00000000..8eff7582 --- /dev/null +++ b/changelogs/client_server/newsfragments/2187.feature @@ -0,0 +1 @@ +Add the `use_state_after` query parameter and `state_after` response property to `GET /sync`, as per [MSC4222](https://github.com/matrix-org/matrix-spec-proposals/issues/4222). diff --git a/changelogs/internal/newsfragments/2115.clarification b/changelogs/internal/newsfragments/2115.clarification deleted file mode 100644 index 3deaf8c8..00000000 --- a/changelogs/internal/newsfragments/2115.clarification +++ /dev/null @@ -1 +0,0 @@ -Add [well-known funding manifest urls](https://floss.fund/funding-manifest/) to spec to authorise https://matrix.org/funding.json. Contributed by @HarHarLinks. diff --git a/changelogs/internal/newsfragments/2157.feature b/changelogs/internal/newsfragments/2157.feature new file mode 100644 index 00000000..58571d8c --- /dev/null +++ b/changelogs/internal/newsfragments/2157.feature @@ -0,0 +1 @@ +Add "placeholder MSC" process definition. \ No newline at end of file diff --git a/changelogs/internal/newsfragments/2172.clarification b/changelogs/internal/newsfragments/2172.clarification new file mode 100644 index 00000000..53f01c68 --- /dev/null +++ b/changelogs/internal/newsfragments/2172.clarification @@ -0,0 +1 @@ +GitHub actions are now building the OpenAPI `spec/identity-service-api/api.json` file. diff --git a/changelogs/internal/newsfragments/2189.clarification b/changelogs/internal/newsfragments/2189.clarification new file mode 100644 index 00000000..f1dc9ec4 --- /dev/null +++ b/changelogs/internal/newsfragments/2189.clarification @@ -0,0 +1 @@ +Specify a correct spelling for "display name". diff --git a/changelogs/internal/newsfragments/2190.clarification b/changelogs/internal/newsfragments/2190.clarification new file mode 100644 index 00000000..019ac021 --- /dev/null +++ b/changelogs/internal/newsfragments/2190.clarification @@ -0,0 +1 @@ +Clarify that clients should replace events with the most recent replacement by origin_server_ts. diff --git a/config/_default/hugo.toml b/config/_default/hugo.toml index 3394c942..abec3958 100644 --- a/config/_default/hugo.toml +++ b/config/_default/hugo.toml @@ -23,15 +23,15 @@ description = "Home of the Matrix specification for decentralised communication" [menus] [[menus.main]] name = 'Foundation' - url = 'https://matrix.org/foundation/' + url = 'https://matrix.org/foundation/about/' weight = 10 [[menus.main]] - name = 'FAQs' - url = 'https://matrix.org/faq' + name = 'User Docs' + url = 'https://matrix.org/docs/' weight = 20 [[menus.main]] name = 'Blog' - url = 'https://matrix.org/blog/posts' + url = 'https://matrix.org/blog/' weight = 30 [markup] @@ -67,7 +67,7 @@ current_version_url = "https://spec.matrix.org/latest" # The following is used when status = "stable", and is displayed in various UI elements on a released version # of the spec. # major = "1" -# minor = "14" +# minor = "15" # User interface configuration [params.ui] diff --git a/content/application-service-api.md b/content/application-service-api.md index 2882f3d9..4c1ada48 100644 --- a/content/application-service-api.md +++ b/content/application-service-api.md @@ -492,10 +492,10 @@ via the query string). It is expected that the application service use the transactions pushed to it to handle events rather than syncing with the user implied by `sender_localpart`. -#### Application service room directories +#### Published room directories -Application services can maintain their own room directories for their -defined third-party protocols. These room directories may be accessed by +Application services can maintain their own published room directories for +their defined third-party protocols. These directories may be accessed by clients through additional parameters on the `/publicRooms` client-server endpoint. diff --git a/content/changelog/v1.15.md b/content/changelog/v1.15.md new file mode 100644 index 00000000..e6f8f243 --- /dev/null +++ b/content/changelog/v1.15.md @@ -0,0 +1,97 @@ +--- +title: v1.15 Changelog +linkTitle: v1.15 +type: docs +layout: changelog +outputs: + - html + - checklist +date: 2025-06-26 +--- + +## Client-Server API + +**New Endpoints** + +- Add `GET /_matrix/client/v1/room_summary/{roomIdOrAlias}`, as per [MSC3266](https://github.com/matrix-org/matrix-spec-proposals/pull/3266). ([#2125](https://github.com/matrix-org/matrix-spec/issues/2125)) +- Add `GET /_matrix/client/v1/auth_metadata`, as per [MSC2965](https://github.com/matrix-org/matrix-spec-proposals/pull/2965). ([#2147](https://github.com/matrix-org/matrix-spec/issues/2147)) + +**Backwards Compatible Changes** + +- Add `m.topic` content block to enable rich text in `m.room.topic` events as per [MSC3765](https://github.com/matrix-org/matrix-spec-proposals/pull/3765). ([#2095](https://github.com/matrix-org/matrix-spec/issues/2095)) +- Include device keys with Olm-encrypted events as per [MSC4147](https://github.com/matrix-org/matrix-spec-proposals/pull/4147). ([#2122](https://github.com/matrix-org/matrix-spec/issues/2122)) +- Add `/_matrix/client/v1/room_summary/{roomIdOrAlias}` and extend `/_matrix/client/v1/rooms/{roomId}/hierarchy` with the new optional properties `allowed_room_ids`, `encryption` and `room_version` as per [MSC3266](https://github.com/matrix-org/matrix-spec-proposals/pull/3266). ([#2125](https://github.com/matrix-org/matrix-spec/issues/2125), [#2158](https://github.com/matrix-org/matrix-spec/issues/2158)) +- Add the OAuth 2.0 based authentication API, as per [MSC3861](https://github.com/matrix-org/matrix-spec-proposals/pull/3861) and its sub-proposals. ([#2141](https://github.com/matrix-org/matrix-spec/issues/2141), [#2148](https://github.com/matrix-org/matrix-spec/issues/2148), [#2149](https://github.com/matrix-org/matrix-spec/issues/2149), [#2150](https://github.com/matrix-org/matrix-spec/issues/2150), [#2151](https://github.com/matrix-org/matrix-spec/issues/2151), [#2159](https://github.com/matrix-org/matrix-spec/issues/2159), [#2164](https://github.com/matrix-org/matrix-spec/issues/2164)) + +**Spec Clarifications** + +- Clarify behaviour when the `topic` key of a `m.room.topic` event is absent, null, or empty. ([#2068](https://github.com/matrix-org/matrix-spec/issues/2068)) +- Fix the example of the `GET /sync` endpoint and the `m.room.member` example used in several places. ([#2077](https://github.com/matrix-org/matrix-spec/issues/2077)) +- Clarify the format of third-party invites, including the fact that identity server public keys can be encoded using standard or URL-safe base64. ([#2083](https://github.com/matrix-org/matrix-spec/issues/2083)) +- "Public" rooms in profile look-ups are defined through their join rule and history visibility. ([#2101](https://github.com/matrix-org/matrix-spec/issues/2101)) +- "Public" rooms in user directory queries are defined through their join rule and history visibility. ([#2102](https://github.com/matrix-org/matrix-spec/issues/2102)) +- Rooms published in `/publicRooms` don't necessarily have `public` join rules or `world_readable` history visibility. ([#2104](https://github.com/matrix-org/matrix-spec/issues/2104)) +- "Public" rooms with respect to call invites are defined through their join rule. ([#2106](https://github.com/matrix-org/matrix-spec/issues/2106)) +- "Public" rooms have no specific meaning with respect to moderation policy lists. ([#2107](https://github.com/matrix-org/matrix-spec/issues/2107)) +- "Public" rooms with respect to presence are defined through their join rule. ([#2108](https://github.com/matrix-org/matrix-spec/issues/2108)) +- Spaces are subject to the same access mechanisms as rooms. ([#2109](https://github.com/matrix-org/matrix-spec/issues/2109)) +- Fix various typos throughout the specification. ([#2121](https://github.com/matrix-org/matrix-spec/issues/2121), [#2144](https://github.com/matrix-org/matrix-spec/issues/2144)) +- Clarify that Well-Known URIs are available on the server name's hostname. Contributed by @HarHarLinks. ([#2140](https://github.com/matrix-org/matrix-spec/issues/2140)) +- Add missing fields in example for `ExportedSessionData`. ([#2154](https://github.com/matrix-org/matrix-spec/issues/2154)) + + +## Server-Server API + +**Backwards Compatible Changes** + +- Add `m.topic` content block to enable rich text in `m.room.topic` events as per [MSC3765](https://github.com/matrix-org/matrix-spec-proposals/pull/3765). ([#2095](https://github.com/matrix-org/matrix-spec/issues/2095)) +- Extend `/_matrix/federation/v1/hierarchy/{roomId}` with the new optional properties `encryption` and `room_version` as per [MSC3266](https://github.com/matrix-org/matrix-spec-proposals/pull/3266). ([#2125](https://github.com/matrix-org/matrix-spec/issues/2125)) + +**Spec Clarifications** + +- Add a note to the invite endpoints that invites to local users may be received twice over federation if the homeserver is already in the room. ([#2067](https://github.com/matrix-org/matrix-spec/issues/2067)) +- Clarify the format of third-party invites, including the fact that identity server public keys can be encoded using standard or URL-safe base64. ([#2083](https://github.com/matrix-org/matrix-spec/issues/2083)) +- Clarify that auth event of `content.join_authorised_via_users_server` is only necessary for `m.room.member` with a `membership` of `join`. ([#2100](https://github.com/matrix-org/matrix-spec/issues/2100)) +- Rooms published in `/publicRooms` don't necessarily have `public` join rules or `world_readable` history visibility. ([#2104](https://github.com/matrix-org/matrix-spec/issues/2104)) +- Fix various typos throughout the specification. ([#2128](https://github.com/matrix-org/matrix-spec/issues/2128)) +- Clarify that Well-Known URIs are available on the server name's hostname. Contributed by @HarHarLinks. ([#2140](https://github.com/matrix-org/matrix-spec/issues/2140)) + + +## Application Service API + +**Spec Clarifications** + +- Clarify in the application service registration schema the `url: null` behaviour. ([#2130](https://github.com/matrix-org/matrix-spec/issues/2130)) + + +## Identity Service API + +**Spec Clarifications** + +- Clarify that public keys can be encoded using standard or URL-safe base64. ([#2083](https://github.com/matrix-org/matrix-spec/issues/2083)) + + +## Push Gateway API + +No significant changes. + + +## Room Versions + +No significant changes. + + +## Appendices + +No significant changes. + + +## Internal Changes/Tooling + +**Spec Clarifications** + +- Adjust margins in rendered endpoints. ([#2081](https://github.com/matrix-org/matrix-spec/issues/2081)) +- Replace Hugo shortcodes in OpenAPI output. ([#2088](https://github.com/matrix-org/matrix-spec/issues/2088)) +- Add [well-known funding manifest urls](https://floss.fund/funding-manifest/) to spec to authorise https://matrix.org/funding.json. Contributed by @HarHarLinks. ([#2115](https://github.com/matrix-org/matrix-spec/issues/2115)) +- Fix the historical info box when generating the historical spec in CI. ([#2123](https://github.com/matrix-org/matrix-spec/issues/2123)) +- Update the header navigation menu with links to modern matrix.org. Contributed by @HarHarLinks. ([#2137](https://github.com/matrix-org/matrix-spec/issues/2137)) diff --git a/content/client-server-api/_index.md b/content/client-server-api/_index.md index 94dfe35f..e7de10eb 100644 --- a/content/client-server-api/_index.md +++ b/content/client-server-api/_index.md @@ -12,6 +12,12 @@ clients which maintain a full local persistent copy of server state. ## API Standards +{{% boxes/note %}} +These standards only apply to the APIs defined in the Matrix specification. APIs +used by this specification but defined in other specifications, like the [OAuth +2.0 API](#oauth-20-api), use their own rules. +{{% /boxes/note %}} + The mandatory baseline for client-server communication in Matrix is exchanging JSON objects over HTTP APIs. More efficient transports may be specified in future as optional extensions. @@ -292,9 +298,8 @@ and the two requests would be considered distinct because the two are considered separate endpoints. Similarly, if a client logs out and back in between two requests using the same transaction ID, the requests are distinct because the act of logging in and out creates a new device (unless an existing -`device_id` is passed to [`POST -/_matrix/client/v3/login`](#post_matrixclientv3login)). On the other hand, if a -client re-uses a transaction ID for the same endpoint after +`device_id` is given during the [login](#login) process). On the other hand, if +a client re-uses a transaction ID for the same endpoint after [refreshing](#refreshing-access-tokens) an access token, it will be assumed to be a duplicate request and ignored. See also [Relationship between access tokens and devices](#relationship-between-access-tokens-and-devices). @@ -371,15 +376,23 @@ valid data was obtained, but no server is available to serve the client. No further guess should be attempted and the user should make a conscientious decision what to do next. -### Well-known URI +### Well-known URIs + +Matrix facilitates automatic discovery for the Client-Server API base URL and more via the +[RFC 8615](https://datatracker.ietf.org/doc/html/rfc8615) "Well-Known URI" method. +This method uses JSON files at a predetermined location on the root path `/.well-known/` to +specify parameter values. {{% boxes/note %}} +Diverging from the rest of the endpoints in the Client-Server spec, these files can not be provided +on the base URL that the Client-Server API is reachable on, as it is yet to be discovered. +Instead, they can be reached via HTTPS on the [server name](/appendices/#server-name)'s hostname as domain. + Servers hosting the `.well-known` JSON file SHOULD offer CORS headers, as per the [CORS](#web-browser-clients) section in this specification. {{% /boxes/note %}} -The `.well-known` method uses a JSON file at a predetermined location to -specify parameter values. The flow for this method is as follows: +The flow for auto-discovery is as follows: 1. Extract the [server name](/appendices/#server-name) from the user's Matrix ID by splitting the Matrix ID at the first colon. @@ -415,16 +428,25 @@ specify parameter values. The flow for this method is as follows: {{% http-api spec="client-server" api="wellknown" %}} -{{% http-api spec="client-server" api="versions" %}} - {{% http-api spec="client-server" api="support" %}} +### API Versions + +Upon connecting, the Matrix client and server need to negotiate which version of the specification +they commonly support, as the API evolves over time. The server advertises its supported versions +and optionally unstable features to the client, which can then go on to make requests to the +endpoints it supports. + +{{% http-api spec="client-server" api="versions" %}} + ## Client Authentication +{{% changed-in v="1.15" %}} OAuth 2.0 API added to the specification. + Most API endpoints require the user to identify themselves by presenting previously obtained credentials in the form of an access token. An access token is typically obtained via the [Login](#login) or -[Registration](#account-registration-and-management) processes. Access tokens +[Registration](#account-registration) processes. Access tokens can expire; a new access token can be generated by using a refresh token. {{% boxes/note %}} @@ -434,6 +456,60 @@ free to choose an appropriate format. Server implementors may like to investigate [macaroons](http://research.google.com/pubs/pub41892.html). {{% /boxes/note %}} +Since Matrix 1.15, the Client-Server specification supports two authentication +APIs: + +* The [legacy API](#legacy-api) +* The [OAuth 2.0 API](#oauth-20-api) + +The legacy API has existed since the first version of the Matrix specification, +while the OAuth 2.0 API has been introduced to rely on a industry standard and +its experience rather than implementing a custom protocol that might not follow +the best practices. + +A homeserver may support one of those two APIs, or both. The two APIs are +mutually incompatible, which means that after logging in, clients MUST only use +the API that was used to obtain their current access token. + +{{% boxes/note %}} +Currently the OAuth 2.0 API doesn't cover all the use cases of the legacy API, +such as automated applications that cannot use a web browser, or +user management by [application services](application-service-api/#server-admin-style-permissions). +{{% /boxes/note %}} + +### Authentication API discovery + +To discover if a homeserver supports the legacy API, the [`GET /login`](#get_matrixclientv3login) +endpoint can be used. + +To discover if a homeserver supports the OAuth 2.0 API, the +[`GET /auth_metadata`](#get_matrixclientv1auth_metadata) endpoint can be used. + +In both cases, the server SHOULD respond with 404 and an `M_UNRECOGNIZED` error +code if the corresponding API is not supported. + +### Account registration + +With the legacy API, a client can register a new account with the +[`/register`](#post_matrixclientv3register) endpoint. + +With the OAuth 2.0 API, a client can't register a new account directly. The end +user must do that directly in the homeserver's web UI. However, the client can +signal to the homeserver that the user wishes to create a new account with the +[`prompt=create`](#user-registration) parameter during authorization. + +### Login + +With the legacy API, a client can obtain an access token by using one of the +[login](#legacy-login) methods supported by the homeserver at the [`POST /login`](#post_matrixclientv3login) +endpoint. To invalidate the access token the client must call the [`/logout`](#post_matrixclientv3logout) +endpoint. + +With the OAuth 2.0 API, a client can obtain an access token by using one of the +[grant types](#grant-types) supported by the homeserver and authorizing the +proper [scope](#scope), as demonstrated in the [login flow](#login-flow). To +invalidate the access token the client must use [token revocation](#token-revocation). + ### Using access tokens Access tokens may be provided via a request header, using the Authentication @@ -479,12 +555,14 @@ used to generate a new access token and refresh token, the new access and refresh tokens are now bound to the device associated with the initial refresh token. -By default, the [Login](#login) and [Registration](#account-registration-and-management) -processes auto-generate a new `device_id`. A client is also free to -generate its own `device_id` or, provided the user remains the same, -reuse a device: in either case the client should pass the `device_id` in -the request body. If the client sets the `device_id`, the server will -invalidate any access and refresh tokens previously assigned to that device. +During login or registration, the generated access token should be associated +with a `device_id`. The legacy [Login](#legacy-login) and [Registration](#legacy-account-registration) +processes auto-generate a new `device_id`, but a client is also free to provide +its own `device_id`. With the OAuth 2.0 API, the `device_id` is always provided +by the client. The client can generate a new `device_id` or, provided the user +remains the same, reuse an existing device. If the client sets the `device_id`, +the server will invalidate any access and refresh tokens previously assigned to +that device. ### Refreshing access tokens @@ -493,14 +571,13 @@ invalidate any access and refresh tokens previously assigned to that device. Access tokens can expire after a certain amount of time. Any HTTP calls that use an expired access token will return with an error code `M_UNKNOWN_TOKEN`, preferably with `soft_logout: true`. When a client receives this error and it -has a refresh token, it should attempt to refresh the access token by calling -[`/refresh`](#post_matrixclientv3refresh). Clients can also refresh their -access token at any time, even if it has not yet expired. If the token refresh -succeeds, the client should use the new token for future requests, and can -re-try previously-failed requests with the new token. When an access token is -refreshed, a new refresh token may be returned; if a new refresh token is -given, the old refresh token will be invalidated, and the new refresh token -should be used when the access token needs to be refreshed. +has a refresh token, it should attempt to refresh the access token. Clients can +also refresh their access token at any time, even if it has not yet expired. If +the token refresh succeeds, the client should use the new token for future +requests, and can re-try previously-failed requests with the new token. When an +access token is refreshed, a new refresh token may be returned; if a new refresh +token is given, the old refresh token will be invalidated, and the new refresh +token should be used when the access token needs to be refreshed. The old refresh token remains valid until the new access token or refresh token is used, at which point the old refresh token is revoked. This ensures that if @@ -513,6 +590,7 @@ and attempt to obtain a new access token by re-logging in. If the error response does not include a `soft_logout: true` property, the client should consider the user as being logged out. +With the legacy API, refreshing access tokens is done by calling [`/refresh`](#post_matrixclientv3refresh). Handling of clients that do not support refresh tokens is up to the homeserver; clients indicate their support for refresh tokens by including a `refresh_token: true` property in the request body of the @@ -522,6 +600,11 @@ may allow the use of non-expiring access tokens, or may expire access tokens anyways and rely on soft logout behaviour on clients that don't support refreshing. +With the OAuth 2.0 API, refreshing access tokens is done with the [refresh token +grant type](#refresh-token-grant), as demonstrated in the [token refresh +flow](#token-refresh-flow). Support for refreshing access tokens is mandatory +with this API. + ### Soft logout A client can be in a "soft logout" state if the server requires @@ -545,9 +628,27 @@ specifying the device ID it is already using to the login API. with an `M_USER_LOCKED` error code, cannot obtain a new access token until the account has been [unlocked](#account-locking). -### User-Interactive Authentication API +### Account management -#### Overview +With the legacy API, a client can use several endpoints to allow the user to +manage their account like [changing their password](#password-management), +[managing their devices](#device-management) or +[deactivating their account](#account-deactivation). + +With the OAuth 2.0 API, all account management is done via the homeserver's web +UI. + +### Legacy API + +This is the original authentication API that was introduced in the first version +of the Client-Server specification and uses custom APIs. Contrary to the OAuth +2.0 API, account management is primarily done in the client's interface and as +such it does not usually require the end user to be redirected to a web UI in +their browser. + +#### User-Interactive Authentication API + +##### Overview Some API endpoints require authentication that interacts with the user. The homeserver may provide many different ways of authenticating, such @@ -571,7 +672,7 @@ the flows in order must result in an HTTP 401 response, as defined below. When all stages in a flow are complete, authentication is complete and the API call succeeds. -#### User-interactive API in the REST API +##### User-interactive API in the REST API In the REST API described in this specification, authentication works by the client and server exchanging JSON dictionaries. The server indicates @@ -749,7 +850,7 @@ 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. {{% /boxes/note %}} -#### Example +**Example** At a high level, the requests made for an API call completing an auth flow with three stages will resemble the following diagram: @@ -791,7 +892,7 @@ flow with three stages will resemble the following diagram: |_______________________| ``` -#### Authentication types +##### Authentication types This specification defines the following auth types: - `m.login.password` @@ -802,7 +903,7 @@ This specification defines the following auth types: - `m.login.dummy` - `m.login.registration_token` -##### Password-based +###### Password-based | Type | Description | @@ -861,7 +962,7 @@ explicitly as follows: In the case that the homeserver does not know about the supplied 3PID, the homeserver must respond with 403 Forbidden. -##### Google ReCaptcha +###### Google ReCaptcha | Type | Description | |---------------------|------------------------------------------------------| @@ -878,7 +979,7 @@ follows: } ``` -##### Single Sign-On +###### Single Sign-On | Type | Description | |---------------|--------------------------------------------------------------------------------------| @@ -888,7 +989,7 @@ A client wanting to complete authentication using SSO should use the [Fallback](#fallback) mechanism. See [SSO during User-Interactive Authentication](#sso-during-user-interactive-authentication) for more information. -##### Email-based (identity / homeserver) +###### Email-based (identity / homeserver) | Type | Description | |--------------------------|------------------------------------------------------------------------------------------------------------------| @@ -917,7 +1018,7 @@ follows: Note that `id_server` (and therefore `id_access_token`) is optional if the [`/requestToken`](#post_matrixclientv3registeremailrequesttoken) request did not include them. -##### Phone number/MSISDN-based (identity / homeserver) +###### Phone number/MSISDN-based (identity / homeserver) | Type | Description | |------------------|----------------------------------------------------------------------------------------------------------------| @@ -946,7 +1047,7 @@ follows: Note that `id_server` (and therefore `id_access_token`) is optional if the [`/requestToken`](#post_matrixclientv3registermsisdnrequesttoken) request did not include them. -##### Dummy Auth +###### Dummy Auth | Type | Description | |------------------|------------------------------------------------------------------------| @@ -972,7 +1073,7 @@ just the type and session, if provided: } ``` -##### Token-authenticated registration +###### Token-authenticated registration {{% added-in v="1.2" %}} @@ -1016,7 +1117,7 @@ in the registration process that their token has expired. {{% http-api spec="client-server" api="registration_tokens" %}} -##### Terms of service at registration +###### Terms of service at registration {{% added-in v="1.11" %}} @@ -1139,7 +1240,7 @@ user during registration, if applicable. {{% definition path="api/client-server/definitions/m.login.terms_params" %}} -#### Fallback +##### Fallback Clients cannot be expected to be able to know how to process every single login type. If a client does not know how to handle a given login @@ -1180,7 +1281,7 @@ with just the session ID: } ``` -##### Example +**Example** A client webapp might use the following JavaScript to open a popup window which will handle unknown login types: @@ -1236,7 +1337,7 @@ function unknownLoginType(homeserverUrl, apiEndpoint, loginType, sessionID, onCo } ``` -#### Identifier types +##### Identifier types Some authentication mechanisms use a user identifier object to identify a user. The user identifier object has a `type` field to indicate the @@ -1249,7 +1350,7 @@ This specification defines the following identifier types: - `m.id.thirdparty` - `m.id.phone` -##### Matrix User ID +###### Matrix User ID | Type | Description | |-------------|--------------------------------------------| @@ -1266,7 +1367,7 @@ ID. } ``` -##### Third-party ID +###### Third-party ID | Type | Description | |-------------------|---------------------------------------------------------------------------| @@ -1286,7 +1387,7 @@ ID media. } ``` -##### Phone number +###### Phone number | Type | Description | |--------------|-------------------------------------------| @@ -1312,7 +1413,7 @@ The `country` is the two-letter uppercase ISO-3166-1 alpha-2 country code that the number in `phone` should be parsed as if it were dialled from. -### Login +#### Login {id="legacy-login"} A client can obtain access tokens using the [`/login`](#post_matrixclientv3login) API. @@ -1384,7 +1485,7 @@ a token for their user ID if supported by the homeserver using {{% http-api spec="client-server" api="logout" %}} -#### Appservice Login +##### Appservice Login {{% added-in v="1.2" %}} @@ -1421,7 +1522,7 @@ If the access token does correspond to an appservice, but the user id does not lie within its namespace then the homeserver will respond with an errcode of `M_EXCLUSIVE`. -#### Login Fallback +##### Login Fallback If a client does not recognize any or all login flows it can use the fallback login API: @@ -1441,11 +1542,13 @@ forwarded to the login endpoint during the login process. For example: GET /_matrix/static/client/login/?device_id=GHTYAJCE -### Account registration and management +#### Account registration {id="legacy-account-registration"} {{% http-api spec="client-server" api="registration" %}} -#### Notes on password management +#### Account management {id="legacy-account-management"} + +##### Password management {{% boxes/warning %}} Clients SHOULD enforce that the password provided is suitably complex. @@ -1454,6 +1557,668 @@ number and a symbol and be at a minimum 8 characters in length. Servers MAY reject weak passwords with an error code `M_WEAK_PASSWORD`. {{% /boxes/warning %}} +{{% http-api spec="client-server" api="password_management" %}} + +##### Account deactivation + +{{% http-api spec="client-server" api="account_deactivation" %}} + +### OAuth 2.0 API + +{{% added-in v="1.15" %}} + +Contrary to the legacy API that uses custom endpoints and UIA, this +authentication API is based on the [OAuth 2.0](https://oauth.net/2/) industry +standard introduced in [RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749) +and extended by other RFCs, with a few features from [OpenID Connect](https://openid.net/connect/). +This allows us to benefit from its experience and from any further enhancements +or best practice recommendations. + +The main change for end users with this API is that all the account management +occurs in their browser on the homeserver's web UI. This is where they will +register a new account or log into an existing account and authorize a client to +access their Matrix account. This means that the homeserver has complete control +over the requirements to create a new account and is not limited by the steps +defined in this specification. It also means that less trust is given to clients +because they don't have access to the user's credentials anymore. + +{{% boxes/warning %}} +The [User-Interactive Authentication API](#user-interactive-authentication-api) +is not compatible with the OAuth 2.0 API, so the endpoints that depend on it for +authentication can't be used when an access token is obtained with this API. +{{% /boxes/warning %}} + +**Sample flow** + +1. [Discover the OAuth 2.0 server metadata](#server-metadata-discovery). +2. [Register the client with the homeserver](#client-registration). +3. [Obtain an access token](#login-flow) by authorizing a [scope](#scope) for the client with the [authorization code grant](#authorization-code-grant). +4. [Refresh the access token](#token-refresh-flow) with the [refresh token grant](#refresh-token-grant) when it expires. +5. [Revoke the tokens](#token-revocation) when the users wants to log out of the client. + +#### Login flow + +Logging in with the OAuth 2.0 API should be done with the [authorization code +grant](#authorization-code-grant). In the context of the Matrix specification, +this means requesting a [scope](#scope) including full client-server API +read/write access and allocating a device ID. + +Once the client has retrieved the [server metadata](#server-metadata-discovery), +it needs to generate the following values: + +- `device_id`: a unique identifier for this device; see the + [`urn:matrix:client:device:`](#device-id-allocation) scope token. +- `state`: a unique opaque identifier, like a [transaction ID](#transaction-identifiers), + that will allow the client to maintain state between the authorization request + and the callback. +- `code_verifier`: a cryptographically random value that will allow to make sure + that the client that makes the token request for a given `code` is the same + one that made the authorization request. + + It is defined in [RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636) as + a high-entropy cryptographic random string using the characters `[A-Z]`, + `[a-z]`, `[0-9]`, `-`, `.`, `_` and `~` with a minimum length of 43 characters + and a maximum length of 128 characters. + +**Authorization request** + +The client then constructs the authorization request URL using the +`authorization_endpoint` value, with the following query parameters: + +| Parameter | Value | +|-------------------------|----------------------------------------------------| +| `response_type` | `code` | +| `client_id` | The client ID returned from client registration. | +| `redirect_uri` | The redirect URI that MUST match one of the values registered in the client metadata | +| `scope` | `urn:matrix:client:api:* urn:matrix:client:device:` with the `device_id` generated previously. | +| `state` | The `state` value generated previously. | +| `response_mode` | `fragment` or `query` (see "[Callback](#callback)" below). | +| `code_challenge` | Computed from the `code_verifier` value generated previously using the SHA-256 algorithm, as described in [RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636). | +| `code_challenge_method` | `S256` | + +This authorization request URL must be opened in the user's browser: + +- For web-based clients, this can be done through a redirection or by opening + the URL in a new tab. +- For native clients, this can be done by opening the URL using the system + browser, or, when available, through platform-specific APIs such as + [`ASWebAuthenticationSession`](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession) + on iOS or [Android Custom Tabs](https://developer.chrome.com/docs/android/custom-tabs). + +Sample authorization request, with extra whitespaces for readability: + +``` +https://account.example.com/oauth2/auth? + client_id = s6BhdRkqt3 & + response_type = code & + response_mode = fragment & + redirect_uri = https://app.example.com/oauth2-callback & + scope = urn:matrix:client:api:* urn:matrix:client:device:AAABBBCCCDDD & + state = ewubooN9weezeewah9fol4oothohroh3 & + code_challenge = 72xySjpngTcCxgbPfFmkPHjMvVDl2jW1aWP7-J6rmwU & + code_challenge_method = S256 +``` + + **Callback** + +Once completed, the user is redirected to the `redirect_uri`, with either a +successful or failed authorization in the URL fragment or query parameters. +Whether the parameters are in the URL fragment or query parameters is determined +by the `response_mode` value: + +- If set to `fragment`, the parameters will be placed in the URL fragment, like + `https://example.com/callback#param1=value1¶m2=value2`. +- If set to `query`, the parameters will be in placed the query string, like + `com.example.app:/callback?param1=value1¶m2=value2`. + +To avoid disclosing the parameters to the web server hosting the `redirect_uri`, +clients SHOULD use the `fragment` response mode if the `redirect_uri` is an +HTTPS URI with a remote host. + +In both success and failure cases, the parameters will include the `state` value +used in the authorization request. + +A successful authorization will have a `code` value, for example: + +``` +https://app.example.com/oauth2-callback#state=ewubooN9weezeewah9fol4oothohroh3&code=iuB7Eiz9heengah1joh2ioy9ahChuP6R +``` + +A failed authorization will have the following values: + +- `error`: the error code +- `error_description`: the error description (optional) +- `error_uri`: the URI where the user can find more information about the error (optional) + +For example: + +``` +https://app.example.com/oauth2-callback#state=ewubooN9weezeewah9fol4oothohroh3&error=access_denied&error_description=The+resource+owner+or+authorization+server+denied+the+request.&error_uri=https%3A%2F%2Ferrors.example.com%2F +``` + +**Token request** + +The client then exchanges the authorization code to obtain an access token using +the token endpoint. + +This is done by making a POST request to the `token_endpoint` with the following +parameters, encoded as `application/x-www-form-urlencoded` in the body: + +| Parameter | Value | +|-----------------|------------------------------------------------------------| +| `grant_type` | `authorization_code` | +| `code` | The value of `code` obtained from the callback. | +| `redirect_uri` | The same `redirect_uri` used in the authorization request. | +| `client_id` | The client ID returned from client registration. | +| `code_verifier` | The value generated at the start of the authorization flow. | + +The server replies with a JSON object containing the access token, the token +type, the expiration time, and the refresh token. + +Sample token request: + +``` +POST /oauth2/token HTTP/1.1 +Host: account.example.com +Content-Type: application/x-www-form-urlencoded +Accept: application/json + +grant_type=authorization_code + &code=iuB7Eiz9heengah1joh2ioy9ahChuP6R + &redirect_uri=https://app.example.com/oauth2-callback + &client_id=s6BhdRkqt3 + &code_verifier=ogie4iVaeteeKeeLaid0aizuimairaCh +``` + +Sample response: + +```json +{ + "access_token": "2YotnFZFEjr1zCsicMWpAA", + "token_type": "Bearer", + "expires_in": 299, + "refresh_token": "tGz3JOkF0XG5Qx2TlKWIA", + "scope": "urn:matrix:client:api:* urn:matrix:client:device:AAABBBCCCDDD" +} +``` + +Finally, the client can call the [`/whoami`](#get_matrixclientv3accountwhoami) +endpoint to get the user ID that owns the access token. + +#### Token refresh flow + +Refreshing a token with the OAuth 2.0 API should be done with the [refresh token +grant](#refresh-token-grant). + +When the access token expires, the client must refresh it by making a `POST` +request to the `token_endpoint` with the following parameters, encoded as +`application/x-www-form-urlencoded` in the body: + +| Parameter | Value | +|-----------------|------------------------------------------------------------| +| `grant_type` | `refresh_token` | +| `refresh_token` | The `refresh_token` obtained from the token response during the last token request. | +| `client_id` | The client ID returned from client registration. | + +The server replies with a JSON object containing the new access token, the token +type, the expiration time, and a new refresh token, like in the authorization +flow. + +#### Server metadata discovery + +{{% http-api spec="client-server" api="oauth_server_metadata" %}} + +#### Client registration + +Before being able to use the authorization flow to obtain an access token, a +client needs to obtain a `client_id` by registering itself with the server. + +This should be done via OAuth 2.0 Dynamic Client Registration as defined in +[RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591). + +##### Client metadata + +In OAuth 2.0, clients register a set of metadata values with the authorization +server, which associates it with a newly generated `client_id`. These values are +used to describe the client to the user and define how the client interacts with +the server. + +{{% definition path="schemas/oauth2-client-metadata" %}} + +###### Metadata localization + +As per [RFC 7591 section 2.2](https://tools.ietf.org/html/rfc7591#section-2.2), +all the human-readable metadata values MAY be localized. + +The human-readable values include: +- `client_name` +- `logo_uri` +- `tos_uri` +- `policy-uri` + +For example: + +```json +{ + "client_name": "Digital mailbox", + "client_name#en-US": "Digital mailbox", + "client_name#en-GB": "Digital postbox", + "client_name#fr": "Boîte aux lettres numérique", + "tos_uri": "https://example.com/tos.html", + "tos_uri#fr": "https://example.com/fr/tos.html", + "policy_uri": "https://example.com/policy.html", + "policy_uri#fr": "https://example.com/fr/policy.html" +} +``` + +###### Redirect URI validation + +The redirect URI plays a critical role in validating the authenticity of the +client. The client "proves" its identity by demonstrating that it controls the +redirect URI. This is why it is critical to have strict validation of the +redirect URI. + +The `application_type` metadata is used to determine the type of client. + +In all cases, the redirect URI MUST NOT have a fragment component. + +**Web clients** + +`web` clients can use redirect URIs that: + +- MUST use the `https` scheme. +- MUST NOT use a user or password in the authority component of the URI. +- MUST use the client URI as a common base for the authority component, as + defined previously. +- MAY include an `application/x-www-form-urlencoded` formatted query component. + +For example, with `https://example.com/` as the client URI, the following are +valid redirect URIs: +- `https://example.com/callback` +- `https://app.example.com/callback` +- `https://example.com:5173/?query=value` + +With the same client URI, the following are invalid redirect URIs: +- `https://example.com/callback#fragment` +- `http://example.com/callback` +- `http://localhost/` + +**Native clients** + +`native` clients can use three types of redirect URIs: + +1. **Private-Use URI Scheme** + - The scheme MUST be prefixed with the client URI hostname in reverse-DNS + notation. For example, if the client URI is `https://example.com/`, then a + valid custom URI scheme would be `com.example.app:/`. + - There MUST NOT be an authority component. This means that the URI MUST have + either a single slash or none immediately following the scheme, with no + hostname, username, or port. +2. **`http` URI on the loopback interface** + - The scheme MUST be `http`. + - The host part MUST be `localhost`, `127.0.0.1`, or `[::1]`. + - There MUST NOT be a port. The homeserver MUST then accept any port number + during the authorization flow. +3. **Claimed `https` Scheme URI** + + Some operating systems allow apps to claim `https` scheme URIs in the + domains they control. When the browser encounters a claimed URI, instead of + the page being loaded in the browser, the native app is launched with the + URI supplied as a launch parameter. The same rules as for `web` clients + apply. + +These restrictions are the same as defined by [RFC 8252 section 7](https://tools.ietf.org/html/rfc8252#section-7). + +For example, with `https://example.com/` as the client URI, + +These are valid redirect URIs: +- `com.example.app:/callback` +- `com.example:/` +- `com.example:callback` +- `http://localhost/callback` +- `http://127.0.0.1/callback` +- `http://[::1]/callback` + +These are invalid redirect URIs: +- `example:/callback` +- `com.example.app://callback` +- `https://localhost/callback` +- `http://localhost:1234/callback` + +##### Dynamic client registration flow + +To register, the client sends an HTTP `POST` request to the +`registration_endpoint`, which can be found in the [server metadata](#server-metadata-discovery). +The body of the request is the JSON-encoded [`OAuthClientMetadata`](#client-metadata). + +For example, the client could send the following registration request: + +```http +POST /register HTTP/1.1 +Content-Type: application/json +Accept: application/json +Server: auth.example.com +``` + +```json +{ + "client_name": "My App", + "client_name#fr": "Mon application", + "client_uri": "https://example.com/", + "logo_uri": "https://example.com/logo.png", + "tos_uri": "https://example.com/tos.html", + "tos_uri#fr": "https://example.com/fr/tos.html", + "policy_uri": "https://example.com/policy.html", + "policy_uri#fr": "https://example.com/fr/policy.html", + "redirect_uris": ["https://app.example.com/callback"], + "token_endpoint_auth_method": "none", + "response_types": ["code"], + "grant_types": [ + "authorization_code", + "refresh_token", + "urn:ietf:params:oauth:grant-type:token-exchange" + ], + "application_type": "web" +} +``` + +Upon successful registration, the server replies with an `HTTP 201 Created` +response, with a JSON object containing the allocated `client_id` and all the +registered metadata values. + +With the registration request above, the server might reply with: + +```json +{ + "client_id": "s6BhdRkqt3", + "client_name": "My App", + "client_uri": "https://example.com/", + "logo_uri": "https://example.com/logo.png", + "tos_uri": "https://example.com/tos.html", + "policy_uri": "https://example.com/policy.html", + "redirect_uris": ["https://app.example.com/callback"], + "token_endpoint_auth_method": "none", + "response_types": ["code"], + "grant_types": ["authorization_code", "refresh_token"], + "application_type": "web" +} +``` + +In this example, the server has not registered the locale-specific values for +`client_name`, `tos_uri`, and `policy_uri`, which is why they are not present in +the response. The server also does not support the +`urn:ietf:params:oauth:grant-type:token-exchange` grant type, which is why it is +not present in the response. + +The client MUST store the `client_id` for future use. + +To avoid the number of client registrations growing over time, the server MAY +choose to delete client registrations that don't have an active session. The +server MUST NOT delete client registrations that have an active session. + +Clients MUST perform a new client registration at the start of each +authorization flow. + +{{% boxes/note %}} +Because each client on each user device will do its own registration, they may +all have different `client_id`s. This means that the server may store the same +client registration multiple times, which could lead to a large number of client +registrations. + +This can be mitigated by de-duplicating client registrations that have identical +metadata. By doing so, different users on different devices using the same +client can share a single `client_id`, reducing the overall number of +registrations. +{{% /boxes/note %}} + +#### Scope + +The client requests a scope in the OAuth 2.0 authorization flow, which is then +associated to the generated access and refresh tokens. This provides a framework +for obtaining user consent. + +A scope is defined in [RFC 6749 section 3.3](https://datatracker.ietf.org/doc/html/rfc6749#section-3.3) +as a string containing a list of space-separated scope tokens. + +{{% boxes/note %}} +The framework encourages the practice of obtaining additional user consent when +a client asks for a new scope that was not granted previously. This could be +used by future MSCs to replace the legacy [User-Interactive Authentication API](#user-interactive-authentication-api). +{{% /boxes/note %}} + +##### Scope token format + +All scope tokens related to Matrix should start with `urn:matrix:` and use the +`:` delimiter for further sub-division. + +Scope tokens related to mapping of Client-Server API access levels should start +with `urn:matrix:client:`. + +{{% boxes/note %}} +For MSCs that build on this namespace, unstable subdivisions should be used +whilst in development. For example, if MSCXXXX wants to introduce the +`urn:matrix:client:foo` scope, it could use +`urn:matrix:client:com.example.mscXXXX.foo` during development. +If it needs to introduce multiple scopes, like `urn:matrix:client:foo` and +`urn:matrix:client:bar`, it could use +`urn:matrix:client:com.example.mscXXXX:foo` and +`urn:matrix:client:com.example.mscXXXX:bar`. +{{% /boxes/note %}} + +##### Allocated scope tokens + +This specification defines the following scope tokens: +- [`urn:matrix:client:api:*`](#full-client-server-api-readwrite-access) +- [`urn:matrix:client:device:`](#device-id-allocation) + +###### Full client-server API read/write access + +| Scope | Purpose | +|---------------------------|---------------------------------------------| +| `urn:matrix:client:api:*` | Grants full access to the Client-Server API. | + +{{% boxes/note %}} +This token matches the behavior of the legacy authentication API. Future MSCs +could introduce more fine-grained scope tokens like +`urn:matrix:client:api:read:*` for read-only access. +{{% /boxes/note %}} + +###### Device ID allocation + +| Scope | Purpose | +|----------------------------------------|----------------------------------------------------------------------------------------------| +| `urn:matrix:client:device:` | Allocates the given `device_id` and associates it to the generated access and refresh tokens. | + +Contrary to the legacy login and registration APIs where the homeserver is +typically the one generating a `device_id` and providing it to the client, with +the OAuth 2.0 API, the client is responsible for allocating the `device_id`. + +There MUST be exactly one `urn:matrix:client:device:` token in the +requested scope in the login flow. + +When generating a new `device_id`, the client SHOULD generate a random string +with enough entropy. It SHOULD only use characters from the unreserved character +list defined by [RFC 3986 section 2.3](https://datatracker.ietf.org/doc/html/rfc3986#section-2.3): + +``` +unreserved = a-z / A-Z / 0-9 / "-" / "." / "_" / "~" +``` + +Using this alphabet, a 10 character string is enough to stand a sufficient +chance of being unique per user. The homeserver MAY reject a request for a +`device_id` that is not long enough or contains characters outside the +unreserved list. + +In any case it MUST only use characters allowed by the OAuth 2.0 scope +definition in [RFC 6749 section 3.3](https://datatracker.ietf.org/doc/html/rfc6749#section-3.3), +which is defined as the following ASCII ranges: + +``` +%x21 / %x23-5B / %x5D-7E +``` + +This definition matches: +- alphanumeric characters: `A-Z`, `a-z`, `0-9` +- the following characters: ``! # $ % & ' ( ) * + , - . / : ; < = > ? @ [ ] ^ _ ` { | } ~`` + +#### Grant types + +[RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749) and other RFCs define +several "grant types": ways to obtain an ["access token"](#using-access-tokens). + +All these grants types require the client to know the following [authorization +server metadata](#server-metadata-discovery): +- `token_endpoint` +- `grant_types_supported` + +The client must also have obtained a `client_id` by [registering with the server](#client-registration). + +This specification supports the following grant types: +- [Authorization code grant](#authorization-code-grant) +- [Refresh token grant](#refresh-token-grant) + +##### Authorization code grant + +As per [RFC 6749 section 4.1](https://datatracker.ietf.org/doc/html/rfc6749#section-4.1), +the authorization code grant lets the client obtain an access token through a +browser redirect. + +This grant requires the client to know the following [authorization server +metadata](#server-metadata-discovery): +- `authorization_endpoint` +- `response_types_supported` +- `response_mode_supported` + +To use this grant, homeservers and clients MUST: + +- Support the authorization code grant as per [RFC 6749 section 4.1](https://datatracker.ietf.org/doc/html/rfc6749#section-4.1). +- Support the [refresh token grant](#refresh-token-grant). +- Support PKCE using the `S256` code challenge method as per [RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636). +- Use [pre-registered](#client-registration), strict redirect URIs. +- Use the `fragment` response mode as per [OAuth 2.0 Multiple Response Type + Encoding Practices](https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html) + for clients with an HTTPS redirect URI. + +###### User registration + +Clients can signal to the server that the user desires to register a new account +by initiating the authorization code grant with the `prompt=create` parameter +set in the authorization request as defined in [Initiating User Registration via +OpenID Connect 1.0](https://openid.net/specs/openid-connect-prompt-create-1_0.html). + +Whether the homeserver supports this parameter is advertised by the +`prompt_values_supported` authorization server metadata. + +Servers that support this parameter SHOULD show the account registration UI in +the browser. + +##### Refresh token grant + +As per [RFC 6749 section 6](https://datatracker.ietf.org/doc/html/rfc6749#section-6), +the refresh token grant lets the client exchange a refresh token for an access +token. + +When authorization is granted to a client, the homeserver MUST issue a refresh +token to the client in addition to the access token. + +The access token MUST be short-lived and SHOULD be refreshed using the +`refresh_token` when expired. + +The homeserver SHOULD issue a new refresh token each time an old one is used, +and invalidate the old one. However, it MUST ensure that the client is able to +retry the refresh request in the case that the response to the request is lost. + +The homeserver SHOULD consider that the session is compromised if an old, +invalidated refresh token is used, and SHOULD revoke the session. + +The client MUST handle access token refresh failures as follows: + + - If the refresh fails due to network issues or a `5xx` HTTP status code from + the server, the client should retry the request with the old refresh token + later. + - If the refresh fails due to a `4xx` HTTP status code from the server, the + client should consider the session logged out. + +#### Token revocation + +When a user wants to log out from a client, the client SHOULD use OAuth 2.0 +token revocation as defined in [RFC 7009](https://datatracker.ietf.org/doc/html/rfc7009). + +The client makes a `POST` request to the `revocation_endpoint` that can be found +in the [authorization server metadata](#server-metadata-discovery). + +The body of the request includes the following parameters, encoded as +`application/x-www-form-urlencoded`: + + + + + + + + + + + + + + + + + + + + + + +
ParameterValue
token + Required. MUST contain either the access token or the + refresh token to be revoked. +
token_type_hint + Optional. If present, MUST have a value of either + access_token or refresh_token. The server MAY + use this value to optimize the token lookup process. +
client_id +

+ Optional. The client identifier obtained during + client registration. +

+

+ If the client_id is not provided, or does not match the + client associated with the token, the server SHOULD still revoke the + token. This behavior is meant to help good actors like secret scanning + tools to proactively revoke leaked tokens. The server MAY also warn + the user that one of their sessions may be compromised in this + scenario. +

+
+ +For example, revoking using the access token: + +``` +POST /oauth2/revoke HTTP/1.1 +Host: auth.example.com +Content-Type: application/x-www-form-urlencoded + +token=mat_ooreiPhei2wequu9fohkai3AeBaec9oo& +token_type_hint=access_token& +client_id=s6BhdRkqt3 +``` + +The server MUST revoke both the access token and refresh token associated with +the token provided in the request. + +The server SHOULD return one of the following responses: + +- If the token is already revoked or invalid, the server returns a `200 OK` + response +- If the client is not authorized to revoke the token, the server returns a + `401 Unauthorized` response +- For other errors, the server returns a `400 Bad Request` response with error + details + +### Account moderation + #### Account locking {{% added-in v="1.12" %}} @@ -2831,10 +3596,42 @@ re-invited. {{% http-api spec="client-server" api="banning" %}} -### Listing rooms +### Published room directory + +Homeservers MAY publish a room directory to allow users to discover rooms. A room +can have one of two visibility settings in the directory: + +- `public`: The room will be shown in the published room directory. +- `private`: The room will be hidden from the published room directory. + +Clients can define a room's initial visibility in the directory via the `visibility` +parameter in [`/createRoom`](#post_matrixclientv3createroom). Irrespective of room +creation, clients can query and change a room's visibility in the directory through +the endpoints listed below, provided that the server permits this. + +{{% boxes/warning %}} +The visibility setting merely defines whether a room is included in the published +room directory or not. It doesn't make any guarantees about the room's +[join rule](#mroomjoin_rules) or [history visibility](#room-history-visibility). + +In particular, a visibility setting of `public` should not be confused with a `public` +join rule. Rooms with a join rule of `knock`, for instance, could reasonably be published +in the directory, too. + +Similarly, a visibility setting of `public` does not necessarily imply a `world_readable` +history visibility. + +To increase performance or by preference, servers MAY apply additional filters when listing the +directory, for instance, by automatically excluding rooms with `invite` join rules +that are not `world_readable` regardless of their visibility. +{{% /boxes/warning %}} {{% http-api spec="client-server" api="list_public_rooms" %}} +### Room Summaries + +{{% http-api spec="client-server" api="room_summary" %}} + ## User Data ### User Directory @@ -2847,10 +3644,15 @@ re-invited. #### Server behaviour -Homeservers MUST at a minimum allow profile look-up for: +Homeservers MUST at a minimum allow profile look-up for users who are +visible to the requester based on their membership in rooms known to the +homeserver. This means: - users that share a room with the requesting user -- users that reside in public rooms known to the homeserver +- users who are joined to rooms known to the homeserver that have a + `public` [join rule](#mroomjoin_rules) +- users who are joined to rooms known to the homeserver that have a + `world_readable` [history visibility](#room-history-visibility) In all other cases, homeservers MAY deny profile look-up by responding with 403 and an error code of `M_FORBIDDEN`. diff --git a/content/client-server-api/modules/end_to_end_encryption.md b/content/client-server-api/modules/end_to_end_encryption.md index 49b053f6..c06178d7 100644 --- a/content/client-server-api/modules/end_to_end_encryption.md +++ b/content/client-server-api/modules/end_to_end_encryption.md @@ -528,7 +528,7 @@ messages, Alice only sends one request event (an event with type `m.room.message` with `msgtype: m.key.verification.request`, rather than an event with type `m.key.verification.request`), to the room. In addition, Alice does not send an `m.key.verification.cancel` event to tell Bob's other devices -that the request as already been accepted; instead, when Bob's other devices +that the request has already been accepted; instead, when Bob's other devices see his `m.key.verification.ready` event, they will know that the request has already been accepted, and that they should ignore the request. @@ -1124,7 +1124,7 @@ The process between Alice and Bob verifying each other would be: framework as described above. 3. Alice's client displays a QR code that Bob is able to scan if Bob's client indicated the ability to scan, an option to scan Bob's QR code if her client - is able to scan. Bob's client prompts displays a QR code that Alice can + is able to scan. Bob's client displays a QR code that Alice can scan if Alice's client indicated the ability to scan, and an option to scan Alice's QR code if his client is able to scan. The format for the QR code is described below. Other options, like starting SAS Emoji verification, @@ -1512,40 +1512,11 @@ message. The plaintext payload is of the form: -```json -{ - "type": "", - "content": "", - "sender": "", - "recipient": "", - "recipient_keys": { - "ed25519": "" - }, - "keys": { - "ed25519": "" - } -} -``` +{{% definition path="api/client-server/definitions/olm_payload" %}} The type and content of the plaintext message event are given in the payload. -Other properties are included in order to prevent an attacker from -publishing someone else's curve25519 keys as their own and subsequently -claiming to have sent messages which they didn't. `sender` must -correspond to the user who sent the event, `recipient` to the local -user, and `recipient_keys` to the local ed25519 key. - -Clients must confirm that the `sender_key` property in the cleartext -`m.room.encrypted` event body, and the `keys.ed25519` property in the -decrypted plaintext, match the keys returned by -[`/keys/query`](#post_matrixclientv3keysquery) for -the given user. Clients must also verify the signature of the keys from the -`/keys/query` response. Without this check, a client cannot be sure that -the sender device owns the private part of the ed25519 key it claims to -have in the Olm payload. This is crucial when the ed25519 key corresponds -to a verified device. - If a client has multiple sessions established with another device, it should use the session from which it last received and successfully decrypted a message. For these purposes, a session that has not received @@ -1555,6 +1526,68 @@ maximum number of olm sessions that it will maintain for each device, and expiring sessions on a Least Recently Used basis. The maximum number of olm sessions maintained per device should be at least 4. +###### Validation of incoming decrypted events + +{{% changed-in v="1.15" %}} Existing checks made more explicit, and checks for `sender_device_keys` added. + +After decrypting an incoming encrypted event, clients MUST apply the +following checks: + +1. The `sender` property in the decrypted content must match the + `sender` of the event. +2. The `keys.ed25519` property in the decrypted content must match + the `sender_key` property in the cleartext `m.room.encrypted` + event body. +3. The `recipient` property in the decrypted content must match + the user ID of the local user. +4. The `recipient_keys.ed25519` property in the decrypted content + must match the client device's [Ed25519 signing key](#device-keys). +5. Where `sender_device_keys` is present in the decrypted content: + 1. `sender_device_keys.user_id` must also match the `sender` + of the event. + 2. `sender_device_keys.keys.ed25519:` must also match + the `sender_key` property in the cleartext `m.room.encrypted` + event body. + 3. `sender_device_keys.keys.curve25519:` must match + the Curve25519 key used to establish the Olm session. + 4. The `sender_device_keys` structure must have a valid signature + from the key with ID `ed25519:` (i.e., the sending + device's Ed25519 key). + +Any event that does not comply with these checks MUST be discarded. + +###### Verification of the sending user for incoming events + +{{% added-in v="1.15" %}} + +In addition, for each Olm session, clients MUST verify that the +Curve25519 key used to establish the Olm session does indeed belong +to the claimed `sender`. This requires a signed "device keys" structure +for that Curve25519 key, which can be obtained in one of two ways: + +1. An Olm message may be received with a `sender_device_keys` property + in the decrypted content. +2. The keys are returned via a [`/keys/query`](#post_matrixclientv3keysquery) + request. Note that both the Curve25519 key **and** the Ed25519 key in + the returned device keys structure must match those used in an + Olm-encrypted event as above. (In particular, the Ed25519 key must + be present in the **encrypted** content of an Olm-encrypted event + to prevent an attacker from claiming another user's Curve25519 key + as their own.) + +Ownership of the Curve25519 key is then established in one of two ways: + +1. Via [cross-signing](#cross-signing). For this to be sufficient, the + device keys structure must be signed by the sender's self-signing key, + and that self-signing key must itself have been validated (either via + [explicit verification](#device-verification) or a "trust on first use" (TOFU) mechanism). +2. Via explicit verification of the device's Ed25519 signing key, as + contained in the device keys structure. This is no longer recommended. + +A failure to complete these verifications does not necessarily mean that +the session is bogus; however it is the case that there is no proof that +the claimed sender is accurate, and the user should be warned accordingly. + ###### Recovering from undecryptable messages Occasionally messages may be undecryptable by clients due to a variety diff --git a/content/client-server-api/modules/event_replacements.md b/content/client-server-api/modules/event_replacements.md index 90c67ecf..e42dacf8 100644 --- a/content/client-server-api/modules/event_replacements.md +++ b/content/client-server-api/modules/event_replacements.md @@ -195,7 +195,7 @@ given event (for example, if an event is edited multiple times). These should be [aggregated](#aggregations-of-child-events) by the homeserver. The aggregation format of `m.replace` relationships gives the **most recent** -replacement event, formatted [as normal](#room-event-format). +valid replacement event, formatted [as normal](#room-event-format). The most recent event is determined by comparing `origin_server_ts`; if two or more replacement events have identical `origin_server_ts`, the event with the @@ -268,6 +268,11 @@ Client authors are reminded to take note of the requirements for [Validity of replacement events](#validity-of-replacement-events), and to ignore any invalid replacement events that are received. +Clients should render the content of the **most recent** valid replacement event. The +most recent event is determined by comparing `origin_server_ts`; if two or more +replacement events have identical `origin_server_ts`, the event with the +lexicographically largest `event_id` is treated as more recent. + ##### Permalinks When creating [links](/appendices/#uris) to events (also known as permalinks), @@ -364,7 +369,7 @@ property under `m.new_content`. A particular constraint applies to events which replace a [reply](#rich-replies): in contrast to the original reply, there should be no `m.in_reply_to` property -in the the `m.relates_to` object, since it would be redundant (see +in the `m.relates_to` object, since it would be redundant (see [Applying `m.new_content`](#applying-mnew_content) above, which notes that the original event's `m.relates_to` is preserved), as well as being contrary to the spirit of the event relationships mechanism which expects only one "parent" per diff --git a/content/client-server-api/modules/moderation_policies.md b/content/client-server-api/modules/moderation_policies.md index 2912d164..15a3a377 100644 --- a/content/client-server-api/modules/moderation_policies.md +++ b/content/client-server-api/modules/moderation_policies.md @@ -18,8 +18,9 @@ the entity making the decisions on filtering is best positioned to interpret the rules how it sees fit. Moderation policy lists are stored as room state events. There are no -restrictions on how the rooms can be configured (they could be public, -private, encrypted, etc). +restrictions on how the rooms can be configured in terms of +[join rules](#mroomjoin_rules), [history visibility](#room-history-visibility), +encryption, etc. There are currently 3 kinds of entities which can be affected by rules: `user`, `server`, and `room`. All 3 are described with diff --git a/content/client-server-api/modules/presence.md b/content/client-server-api/modules/presence.md index ba46b327..4bbd43eb 100644 --- a/content/client-server-api/modules/presence.md +++ b/content/client-server-api/modules/presence.md @@ -68,5 +68,7 @@ will cause the server to automatically set their presence to `online`. #### Security considerations -Presence information is shared with all users who share a room with the -target user. In large public rooms this could be undesirable. +Presence information is published to all users who share a room with the +target user. If the target user is a member of a room with a `public` +[join rule](#mroomjoin_rules), any other user in the federation is +able to gain access to the target user's presence. This could be undesirable. diff --git a/content/client-server-api/modules/search.md b/content/client-server-api/modules/search.md index 8115d5be..2705aa66 100644 --- a/content/client-server-api/modules/search.md +++ b/content/client-server-api/modules/search.md @@ -26,9 +26,10 @@ on certain keys of certain event types. The supported keys to search over are: -- `content.body` in `m.room.message` -- `content.name` in `m.room.name` -- `content.topic` in `m.room.topic` +- `content.body` in [`m.room.message`](/client-server-api/#mroommessage) +- `content.name` in [`m.room.name`](/client-server-api/#mroomname) +- In [`m.room.topic`](/client-server-api/#mroomtopic), `content.topic` + as well as the `body` of the `text/plain` representation in `content['m.topic']`. The search will *not* include rooms that are end to end encrypted. diff --git a/content/client-server-api/modules/spaces.md b/content/client-server-api/modules/spaces.md index 11734ac3..7de41459 100644 --- a/content/client-server-api/modules/spaces.md +++ b/content/client-server-api/modules/spaces.md @@ -2,8 +2,8 @@ {{% added-in v="1.2" %}} -Often used to group rooms of similar subject matter (such as a public "Official -matrix.org rooms" space or personal "Work stuff" space), spaces are a way to +Often used to group rooms of similar subject matter (such as an "Official +matrix.org rooms" space or a "Work stuff" space), spaces are a way to organise rooms while being represented as rooms themselves. A space is defined by the [`m.space` room type](#types), making it known as a @@ -18,11 +18,11 @@ In the default power level structure, this would be `100`. Clients might wish to go a step further and explicitly ignore notification counts on space-rooms. Membership of a space is defined and controlled by the existing mechanisms which -govern a room: [`m.room.member`](#mroommember), [`m.room.history_visibility`](#mroomhistory_visibility), -and [`m.room.join_rules`](#mroomjoin_rules). Public spaces are encouraged to have -a similar setup to public rooms: `world_readable` history visibility, published -canonical alias, and suitably public `join_rule`. Invites, including third-party -invites, still work just as they do in normal rooms as well. +govern a room: [`m.room.member`](/client-server-api#mroommember), [`m.room.history_visibility`](/client-server-api#mroomhistory_visibility), +and [`m.room.join_rules`](/client-server-api#mroomjoin_rules). Canonical aliases and invites, including +third-party invites, still work just as they do in normal rooms as well. Furthermore, +spaces can also be published in the [room directory](/client-server-api#published-room-directory) to make them +discoverable. All other aspects of regular rooms are additionally carried over, such as the ability to set arbitrary state events, hold room account data, etc. Spaces are @@ -87,10 +87,9 @@ the state of `#space:example.org` would consist of: } ``` -No state events in the child rooms themselves would be required (though they -can also be present). This allows for users -to define personal/private spaces to organise their own rooms without needing explicit -permission from the room moderators/admins. +No state events in the child rooms themselves would be required (though they can also +be present). This allows for users to define spaces without needing explicit permission +from the room moderators/admins. Child rooms can be removed from a space by omitting the `via` key of `content` on the relevant state event, such as through redaction or otherwise clearing the `content`. diff --git a/content/client-server-api/modules/sso_login.md b/content/client-server-api/modules/sso_login.md index f50a2eb1..8b228356 100644 --- a/content/client-server-api/modules/sso_login.md +++ b/content/client-server-api/modules/sso_login.md @@ -6,9 +6,10 @@ allow users to log into applications via a single web-based authentication portal. Examples include OpenID Connect, "Central Authentication Service" (CAS) and SAML. -This module allows a Matrix homeserver to delegate user authentication -to an external authentication server supporting one of these protocols. -In this process, there are three systems involved: +This module allows a Matrix homeserver that supports the [legacy authentication +API](#legacy-api) to delegate user authentication to an external authentication +server supporting one of these protocols. In this process, there are three +systems involved: - A Matrix client, using the APIs defined in this specification, which is seeking to authenticate a user to a Matrix homeserver. @@ -24,7 +25,7 @@ used to communicate with the authentication server. Different Matrix homeserver implementations might support different SSO protocols. Clients and homeservers implementing the SSO flow will need to consider -both [login](#login) and [user-interactive authentication](#user-interactive-authentication-api). The flow is +both [login](#legacy-login) and [user-interactive authentication](#user-interactive-authentication-api). The flow is similar in both cases, but there are slight differences. Typically, SSO systems require a single "callback" URI to be configured diff --git a/content/client-server-api/modules/third_party_invites.md b/content/client-server-api/modules/third_party_invites.md index 9ac55f57..2fc8b82e 100644 --- a/content/client-server-api/modules/third_party_invites.md +++ b/content/client-server-api/modules/third_party_invites.md @@ -5,8 +5,8 @@ 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`](#post_matrixclientv3roomsroomidinvite) with the details of the -third-party identifier. +not. Either way, the client calls [`/invite`](#thirdparty_post_matrixclientv3roomsroomidinvite) +with the details of the third-party identifier. The homeserver asks the identity server whether a Matrix user ID is known for that identifier: @@ -37,10 +37,12 @@ A client asks a server to invite a user by their third-party identifier. #### Server behaviour -Upon receipt of an [`/invite`](#post_matrixclientv3roomsroomidinvite), 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: +Upon receipt of an [`/invite`](#thirdparty_post_matrixclientv3roomsroomidinvite), +the server is expected to look up the third-party identifier with the provided +identity server by making a call to [`/_matrix/identity/v2/lookup`](/identity-service-api/#post_matrixidentityv2lookup). +If the lookup yields a result for a Matrix User ID then the normal [invite +process](/server-server-api/#inviting-to-a-room) can be initiated. This process +ends up looking like this: ``` +---------+ +-------------+ +-----------------+ @@ -66,10 +68,11 @@ be initiated. This process ends up looking like this: | | | ``` -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: +However, if the lookup does not yield a bound User ID, the homeserver must store +the invite on the identity server with a call to +[`/_matrix/identity/v2/store-invite`](/identity-service-api/#post_matrixidentityv2store-invite) +and emit a valid [`m.room.third_party_invite`](#mroomthird_party_invite) event +to the room. This process ends up looking like this: ``` +---------+ +-------------+ +-----------------+ @@ -101,15 +104,18 @@ looking like this: | | | ``` -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 +request to [`/_matrix/federation/v1/3pid/onbind`](/server-server-api/#put_matrixfederationv13pidonbind) +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`](#mroommember) event with +`content.membership: invite` and a `content.third_party_invite` property for the +user that has bound the third-party identifier. If the invitee is on a different +homeserver than the inviting user, the invitee's homeserver makes a request to +[`/_matrix/federation/v1/exchange_third_party_invite/{roomId}`](/server-server-api/#put_matrixfederationv1exchange_third_party_inviteroomid). -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. +All homeservers MUST verify the signature in the `m.room.member` event's +`content.third_party_invite.signed` object. 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 @@ -193,8 +199,8 @@ 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](/identity-service-api/#get_matrixidentityv2pubkeyisvalid) +done by making a request to the identity server's +[`/pubkey/isvalid`](/identity-service-api/#get_matrixidentityv2pubkeyisvalid) 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. diff --git a/content/client-server-api/modules/threading.md b/content/client-server-api/modules/threading.md index 5fedbcf8..9f87920a 100644 --- a/content/client-server-api/modules/threading.md +++ b/content/client-server-api/modules/threading.md @@ -185,7 +185,7 @@ included under the `m.relations` property in `unsigned` for the thread root. For ``` `latest_event` is the most recent event (topologically to the server) in the thread sent by an -un-[ignored user](#ignoring-users). +un-[ignored user](#ignoring-users). It should be serialized in the same form as the event itself. Note that, as in the example above, child events of the `latest_event` should themselves be aggregated and included under `m.relations` for that event. The diff --git a/content/client-server-api/modules/voip_events.md b/content/client-server-api/modules/voip_events.md index 14b7d796..1c0bfd28 100644 --- a/content/client-server-api/modules/voip_events.md +++ b/content/client-server-api/modules/voip_events.md @@ -202,11 +202,13 @@ 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 +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. +They should, however, 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 RECOMMENDED that clients ignore call invites in rooms with a +[join rule](#mroomjoin_rules) of `public`. When clients suppress ringing for an +incoming call invite, they SHOULD still display the call invite in the room and annotate that it was ignored. ##### Glare diff --git a/content/proposals.md b/content/proposals.md index b58a98be..0b2c4a16 100644 --- a/content/proposals.md +++ b/content/proposals.md @@ -185,6 +185,10 @@ is as follows: - Take care in creating your proposal. Specify your intended changes, and give reasoning to back them up. Changes without justification will likely be poorly received by the community. + - At the time of creating your draft you will not yet know the PR number, so you + should use a placeholder number to name your file and edit that + after PR submission. The suggested steps are described in + detail [in the proposals guide](https://github.com/matrix-org/matrix-spec-proposals#1-writing-the-proposal). - Fork and make a PR to the [matrix-spec-proposals](https://github.com/matrix-org/matrix-spec-proposals) repository. The ID of your PR will become the MSC ID for the lifetime of your @@ -493,6 +497,42 @@ In summary: a small table at the bottom mapping the various values from stable to unstable. +### Placeholder MSCs + +Some proposals may contain security-sensitive or private context which can't be +publicly disclosed until a later stage in the idea or solution process. Typically, +the initial idea is validated using some amount of implementation or experimentation +and may require an MSC number to make that implementation easier. + +Placeholder MSCs are used to represent proposals in a state where implementation +is ongoing, but the MSC details can't yet be disclosed. Authors which feel as +though their MSC could be highly sensitive MUST get in contact with the Spec Core +Team or [Security Team](https://matrix.org/security-disclosure-policy/) prior to +opening their MSC. If either team determines that a placeholder MSC is required, +it may be opened as such. + +There are a few expectations attached to placeholder MSCs: + +* They have a title which marks them WIP, and are in the "draft" state. +* They have the following labels: `[proposal-placeholder, action-required, needs-implementation]`. + * Notably, *not* `proposal`. +* They are relatively short-lived (ideally less than 6-12 months in placeholder). +* They propose solutions which are reasonably likely to be accepted. If a placeholder + needs to be closed because the idea won't work, isn't needed, etc, then the MSC's + content MUST be published ahead of that closure. + * Note: the MSC's publication (and therefore closure) may be delayed until an + appropriate point in the security disclosure cycle. For example, an alternative + MSC being published, or a stream of work being completed. +* When they are updated to receive real content, the following happens: + 1. The Spec Core Team or the author leaves a comment to cause a notification + that the MSC has been replaced with real content. + 2. The `proposal` label (or its equivalent) is added to trigger chat notifications + in the public Matrix rooms. The `proposal-placeholder` and `action-required` + labels should be removed at this stage as well. Other labels are removed/applied + per normal process. +* The Spec Core Team is aware of the intended MSC's title and purpose. This is + especially important if the Security Team approved the use of a placeholder MSC. + ## Proposal Tracking This is a living document generated from the list of proposals on the diff --git a/content/server-server-api.md b/content/server-server-api.md index d93aa51f..2c52c0e3 100644 --- a/content/server-server-api.md +++ b/content/server-server-api.md @@ -119,7 +119,8 @@ to send. The process overall is as follows: server must present a valid certificate for the hostname. 3. If the hostname is not an IP literal, a regular HTTPS request is - made to `https:///.well-known/matrix/server`, expecting + made to `https:///.well-known/matrix/server` (according to + [RFC 8615](https://datatracker.ietf.org/doc/html/rfc8615)), expecting the schema defined later in this section. 30x redirects should be followed, however redirection loops should be avoided. Responses (successful or otherwise) to the `/.well-known` endpoint should be @@ -543,8 +544,8 @@ the following subset of the room state: `third_party_invite` property, the current `m.room.third_party_invite` event with `state_key` matching `content.third_party_invite.signed.token`, if any. - - If `content.join_authorised_via_users_server` is present, - and the [room version supports restricted rooms](/rooms/#feature-matrix), + - If `membership` is `join`, `content.join_authorised_via_users_server` + is present, and the [room version supports restricted rooms](/rooms/#feature-matrix), then the `m.room.member` event with `state_key` matching `content.join_authorised_via_users_server`. @@ -970,9 +971,8 @@ the event to other servers in the room. ## Third-party invites {{% boxes/note %}} -More information about third-party invites is available in the -[Client-Server API](/client-server-api) under -the Third-party Invites module. +More information about third-party invites is available in the Client-Server API +under the [Third-party invites](/client-server-api/#third-party-invites) module. {{% /boxes/note %}} When a user wants to invite another user in a room but doesn't know the @@ -985,38 +985,41 @@ API](/identity-service-api). ### Cases where an association exists for a third-party identifier -If the third-party identifier is already bound to a Matrix ID, a lookup -request on the identity server will return it. The invite is then -processed by the inviting homeserver as a standard `m.room.member` -invite event. This is the simplest case. +If the third-party identifier is already bound to a Matrix ID, a [lookup +request](/identity-service-api/#post_matrixidentityv2lookup) on the identity +server will return it. The invite is then processed by the inviting homeserver +as a [standard `m.room.member` invite event](#inviting-to-a-room). This is the +simplest case. ### Cases where an association doesn't exist for a third-party identifier If the third-party identifier isn't bound to any Matrix ID, the inviting -homeserver will request the identity server to store an invite for this -identifier and to deliver it to whoever binds it to its Matrix ID. It -will also send an `m.room.third_party_invite` event in the room to -specify a display name, a token and public keys the identity server -provided as a response to the invite storage request. +homeserver will request the identity server to [store an invite](/identity-service-api/#invitation-storage) +for this identifier and to deliver it to whoever binds it to its Matrix ID. It +will also send an [`m.room.third_party_invite`](/client-server-api/#mroomthird_party_invite) +event in the room to specify a display name, a token and public keys the +identity server provided as a response to the invite storage request. -When a third-party identifier with pending invites gets bound to a -Matrix ID, the identity server will send a POST request to the ID's -homeserver as described in the [Invitation -Storage](/identity-service-api#invitation-storage) -section of the Identity Service API. +When a third-party identifier with pending invites gets bound to a Matrix ID, +the identity server will send a request to the [`/3pid/onbind`](#put_matrixfederationv13pidonbind) +endpoint of the the ID's homeserver as described in the [Invitation +Storage](/identity-service-api#invitation-storage) section of the Identity +Service API. The following process applies for each invite sent by the identity server: -The invited homeserver will create an `m.room.member` invite event -containing a special `third_party_invite` section containing the token -and a signed object, both provided by the identity server. +The invited homeserver will create an [`m.room.member`](/client-server-api/#mroommember) +invite event containing a special `third_party_invite` section containing the +token and a `signed` object, both provided by the identity server. If the invited homeserver is in the room the invite came from, it can auth the event and send it. However, if the invited homeserver isn't in the room the invite came -from, it will need to request the room's homeserver to auth the event. +from, it will need to request the inviting homeserver to auth the event +at the [`/exchange_third_party_invite`](#put_matrixfederationv1exchange_third_party_inviteroomid) +endpoint. {{% http-api spec="server-server" api="third_party_invite" %}} @@ -1045,11 +1048,10 @@ user's Matrix ID and the token delivered when the invite was stored, this verification will prove that the `m.room.member` invite event comes from the user owning the invited third-party identifier. -## Public Room Directory +## Published Room Directory -To complement the [Client-Server -API](/client-server-api)'s room directory, -homeservers need a way to query the public rooms for another server. +To complement the [room directory in the Client-Server API](/client-server-api#published-room-directory), +homeservers need a way to query the published rooms of another server. This can be done by making a request to the `/publicRooms` endpoint for the server the room directory should be retrieved for. diff --git a/data/api/application-service/definitions/protocol.yaml b/data/api/application-service/definitions/protocol.yaml index eb56ab26..a91045b9 100644 --- a/data/api/application-service/definitions/protocol.yaml +++ b/data/api/application-service/definitions/protocol.yaml @@ -23,3 +23,4 @@ allOf: type: array items: $ref: protocol_instance.yaml + required: ['instances'] diff --git a/data/api/application-service/definitions/protocol_base.yaml b/data/api/application-service/definitions/protocol_base.yaml index 0238104f..d90f9848 100644 --- a/data/api/application-service/definitions/protocol_base.yaml +++ b/data/api/application-service/definitions/protocol_base.yaml @@ -77,4 +77,4 @@ properties: "placeholder": "#foobar" } } -required: ['user_fields', 'location_fields', 'icon', 'field_types', 'instances'] +required: ['user_fields', 'location_fields', 'icon', 'field_types'] diff --git a/data/api/application-service/definitions/registration.yaml b/data/api/application-service/definitions/registration.yaml index e7e15470..fcf315dc 100644 --- a/data/api/application-service/definitions/registration.yaml +++ b/data/api/application-service/definitions/registration.yaml @@ -20,7 +20,7 @@ properties: type: string description: A unique, user-defined ID of the application service which will never change. url: - type: string + type: ["null", "string"] description: The URL for the application service. May include a path after the domain name. Optionally set to null if no traffic is required. as_token: type: string diff --git a/data/api/client-server/account_deactivation.yaml b/data/api/client-server/account_deactivation.yaml new file mode 100644 index 00000000..467af659 --- /dev/null +++ b/data/api/client-server/account_deactivation.yaml @@ -0,0 +1,141 @@ +# Copyright 2016 OpenMarket Ltd +# Copyright 2022 The Matrix.org Foundation C.I.C. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +openapi: 3.1.0 +info: + title: Matrix Client-Server Account Deactivation API + version: 1.0.0 +paths: + /account/deactivate: + post: + summary: Deactivate a user's account. + description: |- + Deactivate the user's account, removing all ability for the user to + login again. + + This API endpoint uses the [User-Interactive Authentication API](/client-server-api/#user-interactive-authentication-api). + + An access token should be submitted to this endpoint if the client has + an active session. + + The homeserver may change the flows available depending on whether a + valid access token is provided. + + Unlike other endpoints, this endpoint does not take an `id_access_token` + parameter because the homeserver is expected to sign the request to the + identity server instead. + security: + - {} + - accessTokenQuery: [] + - accessTokenBearer: [] + operationId: deactivateAccount + requestBody: + content: + application/json: + schema: + type: object + properties: + auth: + description: Additional authentication information for the user-interactive + authentication API. + allOf: + - $ref: definitions/auth_data.yaml + id_server: + type: string + description: |- + The identity server to unbind all of the user's 3PIDs from. + If not provided, the homeserver MUST use the `id_server` + that was originally use to bind each identifier. If the + homeserver does not know which `id_server` that was, + it must return an `id_server_unbind_result` of + `no-support`. + example: example.org + erase: + x-addedInMatrixVersion: "1.10" + type: boolean + description: |- + Whether the user would like their content to be erased as + much as possible from the server. + + Erasure means that any users (or servers) which join the + room after the erasure request are served redacted copies of + the events sent by this account. Users which had visibility + on those events prior to the erasure are still able to see + unredacted copies. No redactions are sent and the erasure + request is not shared over federation, so other servers + might still serve unredacted copies. + + The server should additionally erase any non-event data + associated with the user, such as [account data](/client-server-api/#client-config) + and [contact 3PIDs](/client-server-api/#adding-account-administrative-contact-information). + + Defaults to `false` if not present. + required: true + responses: + "200": + description: The account has been deactivated. + content: + application/json: + schema: + type: object + properties: + id_server_unbind_result: + type: string + enum: + - success + - no-support + description: |- + An indicator as to whether or not the homeserver was able to unbind + the user's 3PIDs from the identity server(s). `success` indicates + that all identifiers have been unbound from the identity server while + `no-support` indicates that one or more identifiers failed to unbind + due to the identity server refusing the request or the homeserver + being unable to determine an identity server to unbind from. This + must be `success` if the homeserver has no identifiers to unbind + for the user. + example: success + required: + - id_server_unbind_result + "401": + description: The homeserver requires additional authentication information. + content: + application/json: + schema: + $ref: definitions/auth_response.yaml + "429": + description: This request was rate-limited. + content: + application/json: + schema: + $ref: definitions/errors/rate_limited.yaml + tags: + - Account management +servers: + - url: "{protocol}://{hostname}{basePath}" + variables: + protocol: + enum: + - http + - https + default: https + hostname: + default: localhost:8008 + basePath: + default: /_matrix/client/v3 +components: + securitySchemes: + accessTokenQuery: + $ref: definitions/security.yaml#/accessTokenQuery + accessTokenBearer: + $ref: definitions/security.yaml#/accessTokenBearer diff --git a/data/api/client-server/administrative_contact.yaml b/data/api/client-server/administrative_contact.yaml index eddb2b01..54b91d42 100644 --- a/data/api/client-server/administrative_contact.yaml +++ b/data/api/client-server/administrative_contact.yaml @@ -201,6 +201,11 @@ paths: Homeservers should prevent the caller from adding a 3PID to their account if it has already been added to another user's account on the homeserver. + + {{% boxes/warning %}} + Since this endpoint uses User-Interactive Authentication, it cannot be used when the access token was obtained + via the [OAuth 2.0 API](/client-server-api/#oauth-20-api). + {{% /boxes/warning %}} operationId: add3PID security: - accessTokenQuery: [] diff --git a/data/api/client-server/appservice_room_directory.yaml b/data/api/client-server/appservice_room_directory.yaml index b4ec42f0..0443dad2 100644 --- a/data/api/client-server/appservice_room_directory.yaml +++ b/data/api/client-server/appservice_room_directory.yaml @@ -13,18 +13,21 @@ # limitations under the License. openapi: 3.1.0 info: - title: Matrix Client-Server Application Service Room Directory API + title: Matrix Client-Server Application Service Published Room Directory API version: 1.0.0 paths: "/directory/list/appservice/{networkId}/{roomId}": put: - summary: Updates a room's visibility in the application service's room directory. - description: |- - Updates the visibility of a given room on the application service's room + summary: |- + Updates a room's visibility in the application service's published room directory. + description: |- + Updates the visibility of a given room in the application service's + published room directory. - This API is similar to the room directory visibility API used by clients - to update the homeserver's more general room directory. + This API is similar to the + [visibility API](/client-server-api#put_matrixclientv3directorylistroomroomid) + used by clients to update the homeserver's more general published room directory. This API requires the use of an application service access token (`as_token`) instead of a typical client's access_token. This API cannot be invoked by diff --git a/data/api/client-server/create_room.yaml b/data/api/client-server/create_room.yaml index 3992fdfe..339217c5 100644 --- a/data/api/client-server/create_room.yaml +++ b/data/api/client-server/create_room.yaml @@ -87,12 +87,9 @@ paths: - public - private description: |- - A `public` visibility indicates that the room will be shown - in the published room list. A `private` visibility will hide - the room from the published room list. Rooms default to - `private` visibility if this key is not included. NB: This - should not be confused with `join_rules` which also uses the - word `public`. + The room's visibility in the server's + [published room directory](/client-server-api#published-room-directory). + Defaults to `private`. room_alias_name: type: string description: |- @@ -109,15 +106,17 @@ paths: name: type: string description: |- - If this is included, an `m.room.name` event will be sent - into the room to indicate the name of the room. See Room - Events for more information on `m.room.name`. + If this is included, an [`m.room.name`](/client-server-api/#mroomname) event + will be sent into the room to indicate the name for the room. + This overwrites any [`m.room.name`](/client-server-api/#mroomname) + event in `initial_state`. topic: type: string description: |- - If this is included, an `m.room.topic` event will be sent - into the room to indicate the topic for the room. See Room - Events for more information on `m.room.topic`. + If this is included, an [`m.room.topic`](/client-server-api/#mroomtopic) + event with a `text/plain` mimetype will be sent into the room + to indicate the topic for the room. This overwrites any + [`m.room.topic`](/client-server-api/#mroomtopic) event in `initial_state`. invite: type: array description: |- diff --git a/data/api/client-server/cross_signing.yaml b/data/api/client-server/cross_signing.yaml index 8f499d23..60fa9e5b 100644 --- a/data/api/client-server/cross_signing.yaml +++ b/data/api/client-server/cross_signing.yaml @@ -26,7 +26,7 @@ paths: Publishes cross-signing keys for the user. This API endpoint uses the [User-Interactive Authentication API](/client-server-api/#user-interactive-authentication-api). - + User-Interactive Authentication MUST be performed, except in these cases: - there is no existing cross-signing master key uploaded to the homeserver, OR - there is an existing cross-signing master key and it exactly matches the @@ -34,11 +34,16 @@ paths: keys provided in the request (self-signing key, user-signing key) they MUST also match the existing keys stored on the server. In other words, the request contains no new keys. - + This allows clients to freely upload one set of keys, but not modify/overwrite keys if - they already exist. Allowing clients to upload the same set of keys more than once + they already exist. Allowing clients to upload the same set of keys more than once makes this endpoint idempotent in the case where the response is lost over the network, which would otherwise cause a UIA challenge upon retry. + + {{% boxes/warning %}} + When this endpoint requires User-Interactive Authentication, it cannot be used when the access token was obtained + via the [OAuth 2.0 API](/client-server-api/#oauth-20-api). + {{% /boxes/warning %}} operationId: uploadCrossSigningKeys security: - accessTokenQuery: [] diff --git a/data/api/client-server/definitions/key_backup_session_data.yaml b/data/api/client-server/definitions/key_backup_session_data.yaml index e2579142..b5878471 100644 --- a/data/api/client-server/definitions/key_backup_session_data.yaml +++ b/data/api/client-server/definitions/key_backup_session_data.yaml @@ -23,6 +23,7 @@ properties: type: string description: |- The end-to-end message encryption algorithm that the key is for. Must be `m.megolm.v1.aes-sha2`. + example: "m.megolm.v1.aes-sha2" forwarding_curve25519_key_chain: type: array items: @@ -30,31 +31,24 @@ properties: description: |- Chain of Curve25519 keys through which this session was forwarded, via [m.forwarded_room_key](/client-server-api/#mforwarded_room_key) events. + example: [ "hPQNcabIABgGnx3/ACv/jmMmiQHoeFfuLB17tzWp6Hw" ] sender_key: type: string description: |- Unpadded base64-encoded device Curve25519 key. + example: "RF3s+E7RkTQTGF2d8Deol0FkQvgII2aJDf3/Jp5mxVU" sender_claimed_keys: type: object additionalProperties: type: string description: |- A map from algorithm name (`ed25519`) to the Ed25519 signing key of the sending device. + example: { "ed25519": "aj40p+aw64yPIdsxoog8jhPu9i7l7NcFRecuOQblE3Y" } session_key: type: string description: |- Unpadded base64-encoded session key in [session-export format](https://gitlab.matrix.org/matrix-org/olm/blob/master/docs/megolm.md#session-export-format). -example: { - "algorithm": "m.megolm.v1.aes-sha2", - "forwarding_curve25519_key_chain": [ - "hPQNcabIABgGnx3/ACv/jmMmiQHoeFfuLB17tzWp6Hw" - ], - "sender_key": "RF3s+E7RkTQTGF2d8Deol0FkQvgII2aJDf3/Jp5mxVU", - "sender_claimed_keys": { - "ed25519": "aj40p+aw64yPIdsxoog8jhPu9i7l7NcFRecuOQblE3Y", - }, - "session_key": "AgAAAADxKHa9uFxcXzwYoNueL5Xqi69IkD4sni8Llf..." -} + example: "AgAAAADxKHa9uFxcXzwYoNueL5Xqi69IkD4sni8Llf..." required: - algorithm - forwarding_curve25519_key_chain diff --git a/data/api/client-server/definitions/olm_payload.yaml b/data/api/client-server/definitions/olm_payload.yaml new file mode 100644 index 00000000..8bac91e0 --- /dev/null +++ b/data/api/client-server/definitions/olm_payload.yaml @@ -0,0 +1,88 @@ +# Copyright 2025 The Matrix.org Foundation C.I.C +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +type: object +title: OlmPayload +description: |- + The plaintext payload of Olm message events. +properties: + type: + type: string + description: The type of the event. + content: + type: object + description: The event content. + sender: + type: string + description: The user ID of the event sender. + recipient: + type: string + description: The user ID of the intended event recipient. + recipient_keys: + description: The recipient's signing keys of the encrypted event. + $ref: "#/components/schemas/SigningKeys" + keys: + $ref: "#/components/schemas/SigningKeys" + description: The sender's signing keys of the encrypted event. + sender_device_keys: + $ref: device_keys.yaml + description: The sender's device keys. + x-addedInMatrixVersion: "1.15" +required: + - type + - content + - sender + - recipient + - recipient_keys + - keys +components: + schemas: + SigningKeys: + type: object + title: SigningKeys + description: Public keys used for an `m.olm.v1.curve25519-aes-sha2` event. + properties: + ed25519: + type: string + description: The Ed25519 public key encoded using unpadded base64. + required: + - ed25519 +example: { + "type": "", + "content": "", + "sender": "", + "recipient": "", + "recipient_keys": { + "ed25519": "" + }, + "keys": { + "ed25519": "" + }, + "sender_device_keys": { + "algorithms": ["", ""], + "user_id": "", + "device_id": "", + "keys": { + "ed25519:": "", + "curve25519:": "" + }, + "signatures": { + "": { + "ed25519:": "", + "ed25519:": "", + } + } + } +} diff --git a/data/api/client-server/definitions/protocol.yaml b/data/api/client-server/definitions/protocol.yaml index 96db7736..8ffdb6b6 100644 --- a/data/api/client-server/definitions/protocol.yaml +++ b/data/api/client-server/definitions/protocol.yaml @@ -37,7 +37,8 @@ allOf: A unique identifier for this instance on the homeserver. This field is added to the response of [`GET /_matrix/app/v1/thirdparty/protocol/{protocol}`](/application-service-api/#get_matrixappv1thirdpartyprotocolprotocol) by the homeserver. - + This is the identifier to use as the `third_party_instance_id` in a request to [`POST /_matrix/client/v3/publicRooms`](/client-server-api/#post_matrixclientv3publicrooms). example: "irc-freenode" + required: ['instances'] diff --git a/data/api/client-server/definitions/public_rooms_chunk.yaml b/data/api/client-server/definitions/public_rooms_chunk.yaml index 9c6ae6fc..33276662 100644 --- a/data/api/client-server/definitions/public_rooms_chunk.yaml +++ b/data/api/client-server/definitions/public_rooms_chunk.yaml @@ -13,10 +13,12 @@ # limitations under the License. type: object -title: "PublicRoomsChunk" +title: "PublishedRoomsChunk" properties: canonical_alias: type: string + format: mx-room-alias + pattern: "^#" description: The canonical alias of the room, if any. example: "#general:example.org" name: @@ -29,11 +31,15 @@ properties: example: 42 room_id: type: string + format: mx-room-id + pattern: "^!" description: The ID of the room. example: "!abcdefg:example.org" topic: type: string - description: The topic of the room, if any. + description: |- + The plain text topic of the room. Omitted if no `text/plain` mimetype + exists in [`m.room.topic`](/client-server-api/#mroomtopic). example: "All things general" world_readable: type: boolean @@ -59,7 +65,6 @@ properties: example: "public" room_type: type: string - x-addedInMatrixVersion: "1.4" description: |- The `type` of room (from [`m.room.create`](/client-server-api/#mroomcreate)), if any. required: diff --git a/data/api/client-server/definitions/public_rooms_response.yaml b/data/api/client-server/definitions/public_rooms_response.yaml index ba2b712c..4df62b08 100644 --- a/data/api/client-server/definitions/public_rooms_response.yaml +++ b/data/api/client-server/definitions/public_rooms_response.yaml @@ -13,28 +13,15 @@ # limitations under the License. type: object -description: A list of the rooms on the server. +description: A list of the published rooms on the server. required: ["chunk"] properties: chunk: type: array description: |- - A paginated chunk of public rooms. + A paginated chunk of published rooms. items: - allOf: - - $ref: "public_rooms_chunk.yaml" - - type: object - title: PublicRoomsChunk - properties: - # Override description of join_rule - join_rule: - type: string - description: |- - The room's join rule. When not present, the room is assumed to - be `public`. Note that rooms with `invite` join rules are not - expected here, but rooms with `knock` rules are given their - near-public nature. - example: "public" + $ref: "public_rooms_chunk.yaml" next_batch: type: string description: |- @@ -50,7 +37,7 @@ properties: total_room_count_estimate: type: integer description: |- - An estimate on the total number of public rooms, if the + An estimate on the total number of published rooms, if the server has an estimate. example: { "chunk": [ diff --git a/data/api/client-server/definitions/room_summary.yaml b/data/api/client-server/definitions/room_summary.yaml new file mode 100644 index 00000000..cdcfb7cd --- /dev/null +++ b/data/api/client-server/definitions/room_summary.yaml @@ -0,0 +1,44 @@ +# Copyright 2025 The Matrix.org Foundation C.I.C. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +type: object +title: RoomSummary +allOf: + - $ref: public_rooms_chunk.yaml + - type: object + properties: + room_type: + type: string + description: The `type` of room (from + [`m.room.create`](/client-server-api/#mroomcreate)), + if any. + allowed_room_ids: + type: array + items: + type: string + format: mx-room-id + pattern: "^!" + description: |- + If the room is a [restricted room](/server-server-api/#restricted-rooms), these are the room IDs which + are specified by the join rules. Empty or omitted otherwise. + encryption: + type: string + enum: + - "m.megolm.v1.aes-sha2" + description: |- + The encryption algorithm to be used to encrypt messages sent in the + room. + room_version: + description: The version of the room. + type: string diff --git a/data/api/client-server/definitions/security.yaml b/data/api/client-server/definitions/security.yaml index 0c9bd1c7..bb84a08c 100644 --- a/data/api/client-server/definitions/security.yaml +++ b/data/api/client-server/definitions/security.yaml @@ -14,8 +14,8 @@ accessTokenQuery: type: apiKey description: |- - **Deprecated.** The `access_token` returned by a call to `/login` or `/register`, as a query - parameter. + **Deprecated.** The `access_token` obtained during [account registration](/client-server-api/#account-registration) + or [login](/client-server-api/#login), as a query parameter. It can also be the `as_token` of an application service. name: access_token @@ -23,11 +23,11 @@ accessTokenQuery: accessTokenBearer: type: http description: |- - The `access_token` returned by a call to `/login` or `/register`, using the - `Authorization: Bearer` header. + The `access_token` obtained during [account registration](/client-server-api/#account-registration) + or [login](/client-server-api/#login), using the `Authorization: Bearer` header. It can also be the `as_token` of an application service. - + This is the preferred method. scheme: bearer appserviceAccessTokenQuery: @@ -42,6 +42,6 @@ appserviceAccessTokenBearer: description: |- The `as_token` of an application service, using the `Authorization: Bearer` header. - + This is the preferred method. scheme: bearer diff --git a/data/api/client-server/device_management.yaml b/data/api/client-server/device_management.yaml index b2bcb8ce..1b245e78 100644 --- a/data/api/client-server/device_management.yaml +++ b/data/api/client-server/device_management.yaml @@ -137,6 +137,11 @@ paths: This API endpoint uses the [User-Interactive Authentication API](/client-server-api/#user-interactive-authentication-api). Deletes the given device, and invalidates any access token associated with it. + + {{% boxes/warning %}} + Since this endpoint uses User-Interactive Authentication, it cannot be used when the access token was obtained + via the [OAuth 2.0 API](/client-server-api/#oauth-20-api). + {{% /boxes/warning %}} operationId: deleteDevice security: - accessTokenQuery: [] @@ -189,6 +194,11 @@ paths: This API endpoint uses the [User-Interactive Authentication API](/client-server-api/#user-interactive-authentication-api). Deletes the given devices, and invalidates any access token associated with them. + + {{% boxes/warning %}} + Since this endpoint uses User-Interactive Authentication, it cannot be used when the access token was obtained + via the [OAuth 2.0 API](/client-server-api/#oauth-20-api). + {{% /boxes/warning %}} operationId: deleteDevices security: - accessTokenQuery: [] diff --git a/data/api/client-server/list_public_rooms.yaml b/data/api/client-server/list_public_rooms.yaml index ffd202a4..ef71ca63 100644 --- a/data/api/client-server/list_public_rooms.yaml +++ b/data/api/client-server/list_public_rooms.yaml @@ -13,14 +13,15 @@ # limitations under the License. openapi: 3.1.0 info: - title: Matrix Client-Server Room Directory API + title: Matrix Client-Server Published Room Directory API version: 1.0.0 paths: "/directory/list/room/{roomId}": get: summary: Gets the visibility of a room in the directory - description: Gets the visibility of a given room on the server's public room - directory. + description: |- + Gets the visibility of a given room in the server's + published room directory. operationId: getRoomVisibilityOnDirectory parameters: - in: path @@ -32,7 +33,7 @@ paths: type: string responses: "200": - description: The visibility of the room in the directory + description: The visibility of the room in the directory. content: application/json: schema: @@ -50,7 +51,7 @@ paths: "visibility": "public" } "404": - description: The room is not known to the server + description: The room is not known to the server. content: application/json: schema: @@ -64,14 +65,13 @@ paths: tags: - Room discovery put: - summary: Sets the visibility of a room in the room directory + summary: Sets the visibility of a room in the directory description: |- - Sets the visibility of a given room in the server's public room - directory. + Sets the visibility of a given room in the server's published room directory. - Servers may choose to implement additional access control checks - here, for instance that room visibility can only be changed by - the room creator or a server administrator. + Servers MAY implement additional access control checks, for instance, + to ensure that a room's visibility can only be changed by the room creator + or a server administrator. operationId: setRoomVisibilityOnDirectory security: - accessTokenQuery: [] @@ -97,11 +97,11 @@ paths: - public description: |- The new visibility setting for the room. - Defaults to 'public'. + Defaults to `public`. example: { "visibility": "public" } - description: The new visibility for the room on the room directory. + description: The new visibility for the room in the published room directory. required: true responses: "200": @@ -114,7 +114,7 @@ paths: response: value: {} "404": - description: The room is not known to the server + description: The room is not known to the server. content: application/json: schema: @@ -129,9 +129,9 @@ paths: - Room discovery /publicRooms: get: - summary: Lists the public rooms on the server. + summary: Lists a server's published room directory description: |- - Lists the public rooms on the server. + Lists a server's published room directory. This API returns paginated responses. The rooms are ordered by the number of joined members, with the largest rooms first. @@ -154,13 +154,13 @@ paths: - in: query name: server description: |- - The server to fetch the public room lists from. Defaults to the - local server. Case sensitive. + The server to fetch the published room directory from. Defaults + to the local server. Case sensitive. schema: type: string responses: "200": - description: A list of the rooms on the server. + description: A list of the published rooms on the server. content: application/json: schema: @@ -168,9 +168,9 @@ paths: tags: - Room discovery post: - summary: Lists the public rooms on the server with optional filter. + summary: Lists a server's published room directory with an optional filter description: |- - Lists the public rooms on the server, with optional filter. + Lists a server's published room directory with an optional filter. This API returns paginated responses. The rooms are ordered by the number of joined members, with the largest rooms first. @@ -182,8 +182,8 @@ paths: - in: query name: server description: |- - The server to fetch the public room lists from. Defaults to the - local server. Case sensitive. + The server to fetch the published room directory from. Defaults + to the local server. Case sensitive. schema: type: string requestBody: @@ -253,7 +253,7 @@ paths: required: true responses: "200": - description: A list of the rooms on the server. + description: A filtered list of the published rooms on the server. content: application/json: schema: diff --git a/data/api/client-server/logout.yaml b/data/api/client-server/logout.yaml index 60464932..0a555efc 100644 --- a/data/api/client-server/logout.yaml +++ b/data/api/client-server/logout.yaml @@ -47,7 +47,7 @@ paths: deleted alongside the device. This endpoint does not use the [User-Interactive Authentication API](/client-server-api/#user-interactive-authentication-api) because - User-Interactive Authentication is designed to protect against attacks where the + User-Interactive Authentication is designed to protect against attacks where someone gets hold of a single access token then takes over the account. This endpoint invalidates all access tokens for the user, including the token used in the request, and therefore the attacker is unable to take over the account in diff --git a/data/api/client-server/oauth_server_metadata.yaml b/data/api/client-server/oauth_server_metadata.yaml new file mode 100644 index 00000000..4cdb3aa6 --- /dev/null +++ b/data/api/client-server/oauth_server_metadata.yaml @@ -0,0 +1,176 @@ +# Copyright 2025 The Matrix.org Foundation C.I.C. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +openapi: 3.1.0 +info: + title: Matrix Client-Server OAuth 2.0 Server Metadata Discovery API + version: 1.0.0 +paths: + "/auth_metadata": + get: + summary: Get the OAuth 2.0 authorization server metadata. + description: |- + Gets the OAuth 2.0 authorization server metadata, as defined in + [RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414), including the + endpoint URLs and the supported parameters that can be used by the + clients. + + This endpoint definition includes only the fields that are meaningful in + the context of the Matrix specification. The full list of possible + fields is available in the [OAuth Authorization Server Metadata + registry](https://www.iana.org/assignments/oauth-parameters/oauth-parameters.xhtml#authorization-server-metadata), + and normative definitions of them are available in their respective + RFCs. + + {{% boxes/note %}} + The authorization server metadata is relatively large and may change + over time. Clients should: + + - Cache the metadata appropriately based on HTTP caching headers + - Refetch the metadata if it is stale + {{% /boxes/note %}} + operationId: getAuthMetadata + responses: + "200": + description: The OAuth 2.0 authorization server metadata. + content: + application/json: + schema: + type: object + properties: + issuer: + type: string + format: uri + description: |- + The authorization server's issuer identifier, which is a URL that uses the + `https` scheme and has no query or fragment components. + + This is not used in the context of the Matrix specification, but is required + by [RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414). + authorization_endpoint: + type: string + format: uri + description: |- + URL of the authorization endpoint, necessary to use the authorization code + grant. + token_endpoint: + type: string + format: uri + description: |- + URL of the token endpoint, necessary to use the authorization code grant and + the refresh token grant. + revocation_endpoint: + type: string + format: uri + description: |- + URL of the revocation endpoint, necessary to log out a client by invalidating + its access and refresh tokens. + registration_endpoint: + type: string + format: uri + description: |- + URL of the client registration endpoint, necessary to perform dynamic + registration of a client. + response_types_supported: + type: array + description: |- + List of OAuth 2.0 response type strings that the server supports at the + authorization endpoint. + + This array MUST contain at least the `code` value, for clients to be able to + use the authorization code grant. + items: + type: string + description: A response type that the server supports. + grant_types_supported: + type: array + description: |- + List of OAuth 2.0 grant type strings that the server supports at the token + endpoint. + + This array MUST contain at least the `authorization_code` and `refresh_token` + values, for clients to be able to use the authorization code grant and refresh + token grant, respectively. + items: + type: string + description: A grant type that the server supports. + response_modes_supported: + type: array + description: |- + List of OAuth 2.0 response mode strings that the server supports at the + authorization endpoint. + + This array MUST contain at least the `query` and `fragment` values, for + improved security in the authorization code grant. + items: + type: string + description: A response mode that the server supports. + code_challenge_methods_supported: + type: array + description: |- + List of OAuth 2.0 Proof Key for Code Exchange (PKCE) code challenge methods + that the server supports at the authorization endpoint. + + This array MUST contain at least the `S256` value, for improved security in + the authorization code grant. + items: + type: string + description: A PKCE code challenge method that the server supports. + prompt_values_supported: + type: array + description: |- + List of OpenID Connect prompt values that the server supports at the + authorization endpoint. + + Only the `create` value defined in [Initiating User Registration via OpenID + Connect](https://openid.net/specs/openid-connect-prompt-create-1_0.html) is + supported, for a client to signal to the server that the user desires to + register a new account. + items: + type: string + description: A prompt value that the server supports. + required: + - issuer + - authorization_endpoint + - token_endpoint + - revocation_endpoint + - registration_endpoint + - response_types_supported + - grant_types_supported + - response_modes_supported + - code_challenge_methods_supported + example: { + "issuer": "https://account.example.com/", + "authorization_endpoint": "https://account.example.com/oauth2/auth", + "token_endpoint": "https://account.example.com/oauth2/token", + "registration_endpoint": "https://account.example.com/oauth2/clients/register", + "revocation_endpoint": "https://account.example.com/oauth2/revoke", + "response_types_supported": ["code"], + "grant_types_supported": ["authorization_code", "refresh_token"], + "response_modes_supported": ["query", "fragment"], + "code_challenge_methods_supported": ["S256"], + } + tags: + - Session management +servers: + - url: "{protocol}://{hostname}{basePath}" + variables: + protocol: + enum: + - http + - https + default: https + hostname: + default: localhost:8008 + basePath: + default: /_matrix/client/v1 diff --git a/data/api/client-server/password_management.yaml b/data/api/client-server/password_management.yaml new file mode 100644 index 00000000..bf310ff9 --- /dev/null +++ b/data/api/client-server/password_management.yaml @@ -0,0 +1,242 @@ +# Copyright 2016 OpenMarket Ltd +# Copyright 2022 The Matrix.org Foundation C.I.C. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +openapi: 3.1.0 +info: + title: Matrix Client-Server Password Management API + version: 1.0.0 +paths: + /account/password: + post: + summary: Changes a user's password. + description: |- + Changes the password for an account on this homeserver. + + This API endpoint uses the [User-Interactive Authentication API](/client-server-api/#user-interactive-authentication-api) to + ensure the user changing the password is actually the owner of the + account. + + An access token should be submitted to this endpoint if the client has + an active session. + + The homeserver may change the flows available depending on whether a + valid access token is provided. The homeserver SHOULD NOT revoke the + access token provided in the request. Whether other access tokens for + the user are revoked depends on the request parameters. + security: + - {} + - accessTokenQuery: [] + - accessTokenBearer: [] + operationId: changePassword + requestBody: + content: + application/json: + schema: + type: object + properties: + new_password: + type: string + description: The new password for the account. + example: ihatebananas + logout_devices: + type: boolean + description: |- + Whether the user's other access tokens, and their associated devices, should be + revoked if the request succeeds. Defaults to true. + + When `false`, the server can still take advantage of the [soft logout method](/client-server-api/#soft-logout) + for the user's remaining devices. + example: true + auth: + description: Additional authentication information for the user-interactive + authentication API. + allOf: + - $ref: definitions/auth_data.yaml + required: + - new_password + required: true + responses: + "200": + description: The password has been changed. + content: + application/json: + schema: + type: object + examples: + response: + value: {} + "401": + description: The homeserver requires additional authentication information. + content: + application/json: + schema: + $ref: definitions/auth_response.yaml + "429": + description: This request was rate-limited. + content: + application/json: + schema: + $ref: definitions/errors/rate_limited.yaml + tags: + - Account management + /account/password/email/requestToken: + post: + summary: Requests a validation token be sent to the given email address for the + purpose of resetting a user's password + description: |- + The homeserver must check that the given email address **is + associated** with an account on this homeserver. This API should be + used to request validation tokens when authenticating for the + `/account/password` endpoint. + + This API's parameters and response are identical to that of the + [`/register/email/requestToken`](/client-server-api/#post_matrixclientv3registeremailrequesttoken) + endpoint, except that + `M_THREEPID_NOT_FOUND` may be returned if no account matching the + given email address could be found. The server may instead send an + email to the given address prompting the user to create an account. + `M_THREEPID_IN_USE` may not be returned. + + The homeserver should validate the email itself, either by sending a + validation email itself or by using a service it has control over. + operationId: requestTokenToResetPasswordEmail + requestBody: + content: + application/json: + schema: + $ref: definitions/request_email_validation.yaml + required: true + responses: + "200": + description: An email was sent to the given address. + content: + application/json: + schema: + $ref: definitions/request_token_response.yaml + "400": + description: |- + The referenced third-party identifier is not recognised by the + homeserver, or the request was invalid. The error code `M_SERVER_NOT_TRUSTED` + can be returned if the server does not trust/support the identity server + provided in the request. + content: + application/json: + schema: + $ref: definitions/errors/error.yaml + examples: + response: + value: { + "errcode": "M_THREEPID_NOT_FOUND", + "error": "Email not found" + } + "403": + description: |- + The homeserver does not allow the third-party identifier as a + contact option. + content: + application/json: + schema: + $ref: definitions/errors/error.yaml + examples: + response: + value: { + "errcode": "M_THREEPID_DENIED", + "error": "Third-party identifier is not allowed" + } + tags: + - Account management + /account/password/msisdn/requestToken: + post: + summary: Requests a validation token be sent to the given phone number for the + purpose of resetting a user's password. + description: |- + The homeserver must check that the given phone number **is + associated** with an account on this homeserver. This API should be + used to request validation tokens when authenticating for the + `/account/password` endpoint. + + This API's parameters and response are identical to that of the + [`/register/msisdn/requestToken`](/client-server-api/#post_matrixclientv3registermsisdnrequesttoken) + endpoint, except that + `M_THREEPID_NOT_FOUND` may be returned if no account matching the + given phone number could be found. The server may instead send the SMS + to the given phone number prompting the user to create an account. + `M_THREEPID_IN_USE` may not be returned. + + The homeserver should validate the phone number itself, either by sending a + validation message itself or by using a service it has control over. + operationId: requestTokenToResetPasswordMSISDN + requestBody: + content: + application/json: + schema: + $ref: definitions/request_msisdn_validation.yaml + required: true + responses: + "200": + description: An SMS message was sent to the given phone number. + content: + application/json: + schema: + $ref: definitions/request_token_response.yaml + "400": + description: |- + The referenced third-party identifier is not recognised by the + homeserver, or the request was invalid. The error code `M_SERVER_NOT_TRUSTED` + can be returned if the server does not trust/support the identity server + provided in the request. + content: + application/json: + schema: + $ref: definitions/errors/error.yaml + examples: + response: + value: { + "errcode": "M_THREEPID_NOT_FOUND", + "error": "Phone number not found" + } + "403": + description: |- + The homeserver does not allow the third-party identifier as a + contact option. + content: + application/json: + schema: + $ref: definitions/errors/error.yaml + examples: + response: + value: { + "errcode": "M_THREEPID_DENIED", + "error": "Third-party identifier is not allowed" + } + tags: + - Account management +servers: + - url: "{protocol}://{hostname}{basePath}" + variables: + protocol: + enum: + - http + - https + default: https + hostname: + default: localhost:8008 + basePath: + default: /_matrix/client/v3 +components: + securitySchemes: + accessTokenQuery: + $ref: definitions/security.yaml#/accessTokenQuery + accessTokenBearer: + $ref: definitions/security.yaml#/accessTokenBearer diff --git a/data/api/client-server/registration.yaml b/data/api/client-server/registration.yaml index a418d2d2..e7ede561 100644 --- a/data/api/client-server/registration.yaml +++ b/data/api/client-server/registration.yaml @@ -373,315 +373,6 @@ paths: } tags: - Account management - /account/password: - post: - summary: Changes a user's password. - description: |- - Changes the password for an account on this homeserver. - - This API endpoint uses the [User-Interactive Authentication API](/client-server-api/#user-interactive-authentication-api) to - ensure the user changing the password is actually the owner of the - account. - - An access token should be submitted to this endpoint if the client has - an active session. - - The homeserver may change the flows available depending on whether a - valid access token is provided. The homeserver SHOULD NOT revoke the - access token provided in the request. Whether other access tokens for - the user are revoked depends on the request parameters. - security: - - {} - - accessTokenQuery: [] - - accessTokenBearer: [] - operationId: changePassword - requestBody: - content: - application/json: - schema: - type: object - properties: - new_password: - type: string - description: The new password for the account. - example: ihatebananas - logout_devices: - type: boolean - description: |- - Whether the user's other access tokens, and their associated devices, should be - revoked if the request succeeds. Defaults to true. - - When `false`, the server can still take advantage of the [soft logout method](/client-server-api/#soft-logout) - for the user's remaining devices. - example: true - auth: - description: Additional authentication information for the user-interactive - authentication API. - allOf: - - $ref: definitions/auth_data.yaml - required: - - new_password - required: true - responses: - "200": - description: The password has been changed. - content: - application/json: - schema: - type: object - examples: - response: - value: {} - "401": - description: The homeserver requires additional authentication information. - content: - application/json: - schema: - $ref: definitions/auth_response.yaml - "429": - description: This request was rate-limited. - content: - application/json: - schema: - $ref: definitions/errors/rate_limited.yaml - tags: - - Account management - /account/password/email/requestToken: - post: - summary: Requests a validation token be sent to the given email address for the - purpose of resetting a user's password - description: |- - The homeserver must check that the given email address **is - associated** with an account on this homeserver. This API should be - used to request validation tokens when authenticating for the - `/account/password` endpoint. - - This API's parameters and response are identical to that of the - [`/register/email/requestToken`](/client-server-api/#post_matrixclientv3registeremailrequesttoken) - endpoint, except that - `M_THREEPID_NOT_FOUND` may be returned if no account matching the - given email address could be found. The server may instead send an - email to the given address prompting the user to create an account. - `M_THREEPID_IN_USE` may not be returned. - - The homeserver should validate the email itself, either by sending a - validation email itself or by using a service it has control over. - operationId: requestTokenToResetPasswordEmail - requestBody: - content: - application/json: - schema: - $ref: definitions/request_email_validation.yaml - required: true - responses: - "200": - description: An email was sent to the given address. - content: - application/json: - schema: - $ref: definitions/request_token_response.yaml - "400": - description: |- - The referenced third-party identifier is not recognised by the - homeserver, or the request was invalid. The error code `M_SERVER_NOT_TRUSTED` - can be returned if the server does not trust/support the identity server - provided in the request. - content: - application/json: - schema: - $ref: definitions/errors/error.yaml - examples: - response: - value: { - "errcode": "M_THREEPID_NOT_FOUND", - "error": "Email not found" - } - "403": - description: |- - The homeserver does not allow the third-party identifier as a - contact option. - content: - application/json: - schema: - $ref: definitions/errors/error.yaml - examples: - response: - value: { - "errcode": "M_THREEPID_DENIED", - "error": "Third-party identifier is not allowed" - } - tags: - - Account management - /account/password/msisdn/requestToken: - post: - summary: Requests a validation token be sent to the given phone number for the - purpose of resetting a user's password. - description: |- - The homeserver must check that the given phone number **is - associated** with an account on this homeserver. This API should be - used to request validation tokens when authenticating for the - `/account/password` endpoint. - - This API's parameters and response are identical to that of the - [`/register/msisdn/requestToken`](/client-server-api/#post_matrixclientv3registermsisdnrequesttoken) - endpoint, except that - `M_THREEPID_NOT_FOUND` may be returned if no account matching the - given phone number could be found. The server may instead send the SMS - to the given phone number prompting the user to create an account. - `M_THREEPID_IN_USE` may not be returned. - - The homeserver should validate the phone number itself, either by sending a - validation message itself or by using a service it has control over. - operationId: requestTokenToResetPasswordMSISDN - requestBody: - content: - application/json: - schema: - $ref: definitions/request_msisdn_validation.yaml - required: true - responses: - "200": - description: An SMS message was sent to the given phone number. - content: - application/json: - schema: - $ref: definitions/request_token_response.yaml - "400": - description: |- - The referenced third-party identifier is not recognised by the - homeserver, or the request was invalid. The error code `M_SERVER_NOT_TRUSTED` - can be returned if the server does not trust/support the identity server - provided in the request. - content: - application/json: - schema: - $ref: definitions/errors/error.yaml - examples: - response: - value: { - "errcode": "M_THREEPID_NOT_FOUND", - "error": "Phone number not found" - } - "403": - description: |- - The homeserver does not allow the third-party identifier as a - contact option. - content: - application/json: - schema: - $ref: definitions/errors/error.yaml - examples: - response: - value: { - "errcode": "M_THREEPID_DENIED", - "error": "Third-party identifier is not allowed" - } - tags: - - Account management - /account/deactivate: - post: - summary: Deactivate a user's account. - description: |- - Deactivate the user's account, removing all ability for the user to - login again. - - This API endpoint uses the [User-Interactive Authentication API](/client-server-api/#user-interactive-authentication-api). - - An access token should be submitted to this endpoint if the client has - an active session. - - The homeserver may change the flows available depending on whether a - valid access token is provided. - - Unlike other endpoints, this endpoint does not take an `id_access_token` - parameter because the homeserver is expected to sign the request to the - identity server instead. - security: - - {} - - accessTokenQuery: [] - - accessTokenBearer: [] - operationId: deactivateAccount - requestBody: - content: - application/json: - schema: - type: object - properties: - auth: - description: Additional authentication information for the user-interactive - authentication API. - allOf: - - $ref: definitions/auth_data.yaml - id_server: - type: string - description: |- - The identity server to unbind all of the user's 3PIDs from. - If not provided, the homeserver MUST use the `id_server` - that was originally use to bind each identifier. If the - homeserver does not know which `id_server` that was, - it must return an `id_server_unbind_result` of - `no-support`. - example: example.org - erase: - x-addedInMatrixVersion: "1.10" - type: boolean - description: |- - Whether the user would like their content to be erased as - much as possible from the server. - - Erasure means that any users (or servers) which join the - room after the erasure request are served redacted copies of - the events sent by this account. Users which had visibility - on those events prior to the erasure are still able to see - unredacted copies. No redactions are sent and the erasure - request is not shared over federation, so other servers - might still serve unredacted copies. - - The server should additionally erase any non-event data - associated with the user, such as [account data](/client-server-api/#client-config) - and [contact 3PIDs](/client-server-api/#adding-account-administrative-contact-information). - - Defaults to `false` if not present. - required: true - responses: - "200": - description: The account has been deactivated. - content: - application/json: - schema: - type: object - properties: - id_server_unbind_result: - type: string - enum: - - success - - no-support - description: |- - An indicator as to whether or not the homeserver was able to unbind - the user's 3PIDs from the identity server(s). `success` indicates - that all identifiers have been unbound from the identity server while - `no-support` indicates that one or more identifiers failed to unbind - due to the identity server refusing the request or the homeserver - being unable to determine an identity server to unbind from. This - must be `success` if the homeserver has no identifiers to unbind - for the user. - example: success - required: - - id_server_unbind_result - "401": - description: The homeserver requires additional authentication information. - content: - application/json: - schema: - $ref: definitions/auth_response.yaml - "429": - description: This request was rate-limited. - content: - application/json: - schema: - $ref: definitions/errors/rate_limited.yaml - tags: - - Account management /register/available: get: summary: Checks to see if a username is available on the server. diff --git a/data/api/client-server/room_summary.yaml b/data/api/client-server/room_summary.yaml new file mode 100644 index 00000000..e8ee3c8e --- /dev/null +++ b/data/api/client-server/room_summary.yaml @@ -0,0 +1,138 @@ +# Copyright 2025 The Matrix.org Foundation C.I.C. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +openapi: 3.1.0 +info: + title: Matrix Client-Server Room Summary API + version: 1.0.0 +paths: + "/room_summary/{roomIdOrAlias}": + get: + x-addedInMatrixVersion: "1.15" + summary: Retrieves a summary for a room. + description: |- + Retrieves a summary for a room. + + Clients should note that requests for rooms where the user's membership + is `invite` or `knock` might yield outdated, partial or even no data + since the server may not have access to the current state of the room. + + Servers MAY allow unauthenticated access to this API if at least one of + the following conditions holds true: + + - The room has a [join rule](#mroomjoin_rules) of `public`, `knock` or + `knock_restricted`. + - The room has a `world_readable` [history visibility](#room-history-visibility). + + Servers should consider rate limiting requests that require a federation + request more heavily if the client is unauthenticated. + operationId: getRoomSummary + security: + - signedRequest: [] + parameters: + - in: path + name: roomIdOrAlias + description: The room identifier or alias to summarise. + required: true + example: "#monkeys:matrix.org" + schema: + oneOf: + - type: string + format: mx-room-id + pattern: "^!" + - type: string + format: mx-room-alias + pattern: "^#" + - in: query + name: via + description: |- + The servers to attempt to request the summary from when + the local server cannot generate it (for instance, because + it has no local user in the room). + example: + - matrix.org + - elsewhere.ca + schema: + type: array + items: + type: string + format: mx-server-name + responses: + "200": + description: A summary of the room. + content: + application/json: + schema: + description: A summary of the room. + allOf: + - $ref: ../client-server/definitions/room_summary.yaml + - type: object + properties: + membership: + description: |- + The membership state of the user if the user is joined to the room. Absent + if the API was called unauthenticated. + enum: + - invite + - join + - knock + - leave + - ban + type: string + examples: + response: + value: { + room_id: "!ol19s:bleecker.street", + avatar_url: "mxc://bleecker.street/CHEDDARandBRIE", + guest_can_join: false, + name: "CHEESE", + num_joined_members: 37, + topic: "Tasty tasty cheese", + world_readable: true, + join_rule: "public", + room_type: "m.space", + membership: "invite", + encryption: "m.megolm.v1.aes-sha2", + room_version: "9001", + } + "404": + description: |- + The room could not be found. + content: + application/json: + schema: + $ref: ../client-server/definitions/errors/error.yaml + examples: + response: + value: { + "errcode": "M_NOT_FOUND", + "error": "Room not found." + } +servers: + - url: "{protocol}://{hostname}{basePath}" + variables: + protocol: + enum: + - http + - https + default: https + hostname: + default: localhost:8008 + basePath: + default: /_matrix/client/v1 +components: + securitySchemes: + accessTokenQuery: + $ref: definitions/security.yaml#/accessTokenQuery + accessTokenBearer: + $ref: definitions/security.yaml#/accessTokenBearer diff --git a/data/api/client-server/rooms.yaml b/data/api/client-server/rooms.yaml index a2197f34..a5c9977e 100644 --- a/data/api/client-server/rooms.yaml +++ b/data/api/client-server/rooms.yaml @@ -105,13 +105,31 @@ paths: example: "" schema: type: string + - in: query + name: format + x-addedInMatrixVersion: "1.16" + description: |- + The format to use for the returned data. `content` (the default) will + return only the content of the state event. `event` will return the entire + event in the usual format suitable for clients, including fields like event + ID, sender and timestamp. + example: event + schema: + type: string + enum: + - content + - event responses: "200": - description: The content of the state event. + description: |- + The content of the state event, or the entire client-formatted event + if `?format=event` was used. content: application/json: schema: - type: object + oneOf: + - type: object + - $ref: "../../event-schemas/schema/core-event-schema/state_event.yaml" examples: response: value: { diff --git a/data/api/client-server/space_hierarchy.yaml b/data/api/client-server/space_hierarchy.yaml index 3a4a14a5..5f6657a2 100644 --- a/data/api/client-server/space_hierarchy.yaml +++ b/data/api/client-server/space_hierarchy.yaml @@ -102,15 +102,10 @@ paths: * The room's [`m.room.history_visibility`](#room-history-visibility) is set to `world_readable`. items: allOf: - - $ref: definitions/public_rooms_chunk.yaml + - $ref: definitions/room_summary.yaml - type: object title: SpaceHierarchyRoomsChunk properties: - room_type: - type: string - description: The `type` of room (from - [`m.room.create`](/client-server-api/#mroomcreate)), - if any. children_state: type: array description: |- @@ -130,6 +125,14 @@ paths: description: The `origin_server_ts` for the event. required: - origin_server_ts + room_type: + x-addedInMatrixVersion: "1.4" # Extends room_summary.yaml + allowed_room_ids: + x-addedInMatrixVersion: "1.15" # Extends room_summary.yaml + encryption: + x-addedInMatrixVersion: "1.15" # Extends room_summary.yaml + room_version: + x-addedInMatrixVersion: "1.15" # Extends room_summary.yaml required: - children_state next_batch: diff --git a/data/api/client-server/support.yaml b/data/api/client-server/support.yaml index b9ca062e..154afbe7 100644 --- a/data/api/client-server/support.yaml +++ b/data/api/client-server/support.yaml @@ -22,9 +22,12 @@ paths: description: |- Gets server admin contact and support page of the domain. - Like the [well-known discovery URI](/client-server-api/#well-known-uri), - this should be accessed with the hostname of the homeserver by making a + {{% boxes/note %}} + Like the [well-known discovery URI](/client-server-api/#well-known-uris), + this endpoint should be accessed with the hostname of the homeserver's + [server name](/appendices/#server-name) by making a GET request to `https://hostname/.well-known/matrix/support`. + {{% /boxes/note %}} Note that this endpoint is not necessarily handled by the homeserver. It may be served by another webserver, used for discovering support diff --git a/data/api/client-server/sync.yaml b/data/api/client-server/sync.yaml index 9d9a807d..96680180 100644 --- a/data/api/client-server/sync.yaml +++ b/data/api/client-server/sync.yaml @@ -117,6 +117,31 @@ paths: example: 30000 schema: type: integer + - in: query + name: use_state_after + x-addedInMatrixVersion: "1.16" + description: |- + Controls whether to receive state changes between the previous sync + and the **start** of the timeline, or between the previous sync and + the **end** of the timeline. + + If this is set to `true`, servers MUST respond with the state + between the previous sync and the **end** of the timeline in + `state_after` and MUST omit `state`. + + If `false`, servers MUST respond with the state between the previous + sync and the **start** of the timeline in `state` and MUST omit + `state_after`. + + Even if this is set to `true`, clients MUST update their local state + with events in `state` and `timeline` if `state_after` is missing in + the response, for compatibility with servers that don't support this + parameter. + + By default, this is `false`. + example: false + schema: + type: boolean responses: "200": description: The initial snapshot or delta for the client to use to update their @@ -197,16 +222,50 @@ paths: type: object description: |- Updates to the state, between the time indicated by - the `since` parameter, and the start of the - `timeline` (or all state up to the start of the + the `since` parameter, and the **start** of the + `timeline` (or all state up to the **start** of the `timeline`, if `since` is not given, or `full_state` is true). - N.B. state updates for `m.room.member` events will + {{% boxes/note %}} + State updates for `m.room.member` events will be incomplete if `lazy_load_members` is enabled in the `/sync` filter, and only return the member events required to display the senders of the timeline events in this response. + {{% /boxes/note %}} + + MUST be omitted if `use_state_after` was set to `true` + in the request. + allOf: + - $ref: definitions/state_event_batch.yaml + state_after: + title: State + type: object + x-addedInMatrixVersion: "1.16" + description: |- + Updates to the state, between the time indicated by + the `since` parameter, and the **end** of the + `timeline` (or all state up to the **end** of the + `timeline`, if `since` is not given, or + `full_state` is true). + + {{% boxes/note %}} + State updates for `m.room.member` events will + be incomplete if `lazy_load_members` is enabled in + the `/sync` filter, and only return the member events + required to display the senders of the timeline events + in this response. + {{% /boxes/note %}} + + If this field is set, even if it is empty, clients MUST + only update their local state with events in this list, + and MUST NOT update their local state with events in + `timeline`. If this field is not set, clients MUST update + their local state with events in `state` and `timeline`. + + **Required** if `use_state_after` was set to `true` in the + request, even if it is empty. allOf: - $ref: definitions/state_event_batch.yaml timeline: @@ -353,7 +412,28 @@ paths: state: title: State type: object - description: The state updates for the room up to the start of the timeline. + description: |- + The state updates for the room up to the **start** of the timeline. + + MUST be omitted if `use_state_after` was set to `true` in the + request. + allOf: + - $ref: definitions/state_event_batch.yaml + state_after: + title: State + type: object + x-addedInMatrixVersion: "1.16" + description: |- + The state updates for the room up to the **end** of the timeline. + + If this field is set, even if it is empty, clients MUST only + update their local state with events in this list, and MUST NOT + update their local state with events in `timeline`. If this field + is not set, clients MUST update their local state with events in + `state` and `timeline`. + + **Required** if `use_state_after` was set to `true` in the + request, even if it is empty. allOf: - $ref: definitions/state_event_batch.yaml timeline: @@ -441,17 +521,57 @@ paths: "state": { "events": [ { - "$ref": "../../event-schemas/examples/m.room.member.yaml" + "content": { + "avatar_url": "mxc://example.org/SFHyPlCeYUSFFxlgbQYZmoEoe", + "displayname": "Example user", + "membership": "join" + }, + "event_id": "$143273976499sgjks:example.org", + "origin_server_ts": 1432735824653, + "sender": "@example:example.org", + "state_key": "@example:example.org", + "type": "m.room.member", + "unsigned": { + "age": 45603, + "membership": "join" + } } ] }, "timeline": { "events": [ { - "$ref": "../../event-schemas/examples/m.room.member.yaml" + "content": { + "avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF", + "displayname": "Alice Margatroid", + "membership": "join", + "reason": "Looking for support" + }, + "event_id": "$143273582443PhrSn:example.org", + "origin_server_ts": 1432735824653, + "sender": "@alice:example.org", + "state_key": "@alice:example.org", + "type": "m.room.member", + "unsigned": { + "age": 1234, + "membership": "join" + } }, { - "$ref": "../../event-schemas/examples/m.room.message$m.text.yaml" + "content": { + "body": "This is an example text message", + "format": "org.matrix.custom.html", + "formatted_body": "This is an example text message", + "msgtype": "m.text" + }, + "event_id": "$143273582443PhrSn:example.org", + "origin_server_ts": 1432735824653, + "sender": "@example:example.org", + "type": "m.room.message", + "unsigned": { + "age": 1234, + "membership": "join" + } } ], "limited": true, diff --git a/data/api/client-server/third_party_membership.yaml b/data/api/client-server/third_party_membership.yaml index 65148ee0..67caa387 100644 --- a/data/api/client-server/third_party_membership.yaml +++ b/data/api/client-server/third_party_membership.yaml @@ -57,9 +57,6 @@ paths: - A signature of the token, signed with the identity server's private key - The matrix user ID who invited them to the room - - If a token is requested from the identity server, the homeserver will - append a `m.room.third_party_invite` event to the room. operationId: inviteBy3PID security: - accessTokenQuery: [] @@ -72,6 +69,8 @@ paths: example: "!d41d8cd:matrix.org" schema: type: string + format: mx-room-id + pattern: "^!" requestBody: content: application/json: @@ -90,7 +89,9 @@ paths: value: {} "403": description: |- - You do not have permission to invite the user to the room. A meaningful `errcode` and description error text will be returned. Example reasons for rejections are: + You do not have permission to invite the user to the room. A + meaningful `errcode` and description error text will be returned. + Example reasons for rejections are: - The invitee has been banned from the room. - The invitee is already a member of the room. diff --git a/data/api/client-server/users.yaml b/data/api/client-server/users.yaml index 3a7f2bfa..c1f3c6c8 100644 --- a/data/api/client-server/users.yaml +++ b/data/api/client-server/users.yaml @@ -20,10 +20,17 @@ paths: post: summary: Searches the user directory. description: |- - Performs a search for users. The homeserver may - determine which subset of users are searched, however the homeserver - MUST at a minimum consider the users the requesting user shares a - room with and those who reside in public rooms (known to the homeserver). + Performs a search for users. The homeserver may determine which + subset of users are searched. However, the homeserver MUST at a + minimum consider users who are visible to the requester based + on their membership in rooms known to the homeserver. This means: + + - users that share a room with the requesting user + - users who are joined to rooms known to the homeserver that have a + `public` [join rule](#mroomjoin_rules) + - users who are joined to rooms known to the homeserver that have a + `world_readable` [history visibility](#room-history-visibility) + The search MUST consider local users to the homeserver, and SHOULD query remote users as part of the search. diff --git a/data/api/client-server/wellknown.yaml b/data/api/client-server/wellknown.yaml index 72de3598..4e2383bf 100644 --- a/data/api/client-server/wellknown.yaml +++ b/data/api/client-server/wellknown.yaml @@ -26,6 +26,12 @@ paths: suitably namespaced for each application and reduces the risk of clashes. + {{% boxes/note %}} + This endpoint should be accessed with the hostname of the homeserver's + [server name](/appendices/#server-name) by making a + GET request to `https://hostname/.well-known/matrix/client`. + {{% /boxes/note %}} + Note that this endpoint is not necessarily handled by the homeserver, but by another webserver, to be used for discovering the homeserver URL. operationId: getWellknown diff --git a/data/api/identity/v2_pubkey.yaml b/data/api/identity/v2_pubkey.yaml index 2aaf5f8e..68e383a8 100644 --- a/data/api/identity/v2_pubkey.yaml +++ b/data/api/identity/v2_pubkey.yaml @@ -43,7 +43,8 @@ paths: properties: public_key: type: string - description: Unpadded Base64 encoded public key. + description: |- + [Unpadded Base64](/appendices/#unpadded-base64)-encoded public key. required: - public_key examples: @@ -74,7 +75,8 @@ paths: - in: query name: public_key required: true - description: The unpadded base64-encoded public key to check. + description: |- + The [unpadded Base64](/appendices/#unpadded-base64)-encoded public key to check. example: VXuGitF39UH5iRfvbIknlvlAVKgD1BsLDMvBf0pmp7c schema: type: string @@ -105,7 +107,14 @@ paths: - in: query name: public_key required: true - description: The unpadded base64-encoded public key to check. + description: |- + The [unpadded Base64](/appendices/#unpadded-base64)-encoded public + key to check. + + This MUST be the exact same encoded string returned in the response + of the [`/store-invite`](/identity-service-api/#post_matrixidentityv2store-invite) + endpoint, or found in the corresponding [`m.room.third_party_invite`](/client-server-api/#mroomthird_party_invite) + event, so it may use the standard or URL-safe alphabets. example: VXuGitF39UH5iRfvbIknlvlAVKgD1BsLDMvBf0pmp7c schema: type: string diff --git a/data/api/identity/v2_store_invite.yaml b/data/api/identity/v2_store_invite.yaml index 5dcd9a78..98e7e7f4 100644 --- a/data/api/identity/v2_store_invite.yaml +++ b/data/api/identity/v2_store_invite.yaml @@ -42,7 +42,7 @@ paths: (if present) from the request here. Also, the generated ephemeral public key will be listed as valid on - requests to `/_matrix/identity/v2/pubkey/ephemeral/isvalid`. + requests to [`/_matrix/identity/v2/pubkey/ephemeral/isvalid`](/identity-service-api/#get_matrixidentityv2pubkeyephemeralisvalid). Currently, invites may only be issued for 3pids of the `email` medium. @@ -70,10 +70,14 @@ paths: room_id: type: string description: The Matrix room ID to which the user is invited + format: mx-room-id + pattern: "^!" example: "!something:example.org" sender: type: string description: The Matrix user ID of the inviting user + format: mx-user-id + pattern: "^@" example: "@bob:example.com" room_alias: type: string @@ -81,12 +85,16 @@ paths: The Matrix room alias for the room to which the user is invited. This should be retrieved from the `m.room.canonical_alias` state event. + format: mx-room-alias + pattern: "^#" example: "#somewhere:example.org" room_avatar_url: type: string description: |- The Content URI for the room to which the user is invited. This should be retrieved from the `m.room.avatar` state event. + format: mx-mxc-uri + pattern: "^mxc:\\/\\/" example: mxc://example.org/s0meM3dia room_join_rules: type: string @@ -108,6 +116,8 @@ paths: type: string description: The Content URI for the avatar of the user ID initiating the invite. + format: mx-mxc-uri + pattern: "^mxc:\\/\\/" example: mxc://example.org/an0th3rM3dia room_type: type: string @@ -146,7 +156,7 @@ paths: public_key: type: string description: | - The public key, encoded using [unpadded Base64](/appendices/#unpadded-base64). + The public key, encoded using standard or URL-safe [unpadded Base64](/appendices/#unpadded-base64). key_validity_url: type: string description: | diff --git a/data/api/server-server/invites-v1.yaml b/data/api/server-server/invites-v1.yaml index 7d241c37..b1771018 100644 --- a/data/api/server-server/invites-v1.yaml +++ b/data/api/server-server/invites-v1.yaml @@ -20,7 +20,7 @@ paths: put: summary: Invites a remote user to a room description: |- - Invites a remote user to a room. Once the event has been signed by both the inviting + Invites a remote user to a room. Once the event has been signed by both the inviting homeserver and the invited homeserver, it can be sent to all of the servers in the room by the inviting homeserver. @@ -32,6 +32,10 @@ paths: [room version specification](/rooms) for precise event formats. **The request and response bodies here describe the common event fields in more detail and may be missing other required fields for a PDU.** + + Also note that if the remote homeserver is already in the room, it will receive the + invite event twice; once through this endpoint, and again through a [federation + transaction](/server-server-api/#transactions). operationId: sendInviteV1 security: - signedRequest: [] diff --git a/data/api/server-server/invites-v2.yaml b/data/api/server-server/invites-v2.yaml index f42c8eea..6ac8bb3e 100644 --- a/data/api/server-server/invites-v2.yaml +++ b/data/api/server-server/invites-v2.yaml @@ -24,7 +24,7 @@ paths: This API is nearly identical to the v1 API with the exception of the request body being different, and the response format fixed. - Invites a remote user to a room. Once the event has been signed by both the inviting + Invites a remote user to a room. Once the event has been signed by both the inviting homeserver and the invited homeserver, it can be sent to all of the servers in the room by the inviting homeserver. @@ -36,6 +36,10 @@ paths: [room version specification](/rooms) for precise event formats. **The request and response bodies here describe the common event fields in more detail and may be missing other required fields for a PDU.** + + Also note that if the remote homeserver is already in the room, it will receive the + invite event twice; once through this endpoint, and again through a [federation + transaction](/server-server-api/#transactions). operationId: sendInviteV2 security: - signedRequest: [] diff --git a/data/api/server-server/public_rooms.yaml b/data/api/server-server/public_rooms.yaml index 565f1aa4..8beaecb2 100644 --- a/data/api/server-server/public_rooms.yaml +++ b/data/api/server-server/public_rooms.yaml @@ -13,16 +13,20 @@ # limitations under the License. openapi: 3.1.0 info: - title: Matrix Federation Public Rooms API + title: Matrix Federation Published Room Directory API version: 1.0.0 paths: /publicRooms: get: - summary: Get all the public rooms for a homeserver + summary: Lists the server's published room directory description: |- - Gets all the public rooms for the homeserver. This should not return - rooms that are listed on another homeserver's directory, just those - listed on the receiving homeserver's directory. + Lists the server's published room directory. + + This API returns paginated responses. The rooms are ordered by the number + of joined members, with the largest rooms first. + + This SHOULD not return rooms that are listed on another homeserver's directory, + just those listed on the receiving homeserver's directory. operationId: getPublicRooms security: - signedRequest: [] @@ -62,21 +66,18 @@ paths: type: string responses: "200": - description: The public room list for the homeserver. + description: A list of the published rooms on the server. content: application/json: schema: $ref: ../client-server/definitions/public_rooms_response.yaml post: - summary: Gets the public rooms on the server with optional filter. + summary: Lists the server's published room directory with an optional filter description: |- - Lists the public rooms on the server, with optional filter. + Lists the server's published room directory with an optional filter. This API returns paginated responses. The rooms are ordered by the number of joined members, with the largest rooms first. - - Note that this endpoint receives and returns the same format that is seen - in the Client-Server API's `POST /publicRooms` endpoint. operationId: queryPublicRooms security: - signedRequest: [] @@ -147,69 +148,11 @@ paths: required: true responses: "200": - description: A list of the rooms on the server. + description: A filtered list of the published rooms on the server. content: application/json: schema: - type: object - description: A list of the rooms on the server. - required: - - chunk - properties: - chunk: - title: PublicRoomsChunks - type: array - description: A paginated chunk of public rooms. - items: - allOf: - - $ref: ../client-server/definitions/public_rooms_chunk.yaml - - type: object - properties: - # Override description of join_rule - join_rule: - type: string - description: |- - The room's join rule. When not present, the room is assumed to - be `public`. Note that rooms with `invite` join rules are not - expected here, but rooms with `knock` rules are given their - near-public nature. - next_batch: - type: string - description: |- - A pagination token for the response. The absence of this token - means there are no more results to fetch and the client should - stop paginating. - prev_batch: - type: string - description: |- - A pagination token that allows fetching previous results. The - absence of this token means there are no results before this - batch, i.e. this is the first batch. - total_room_count_estimate: - type: integer - description: |- - An estimate on the total number of public rooms, if the - server has an estimate. - examples: - response: - value: { - "chunk": [ - { - "avatar_url": "mxc://bleecker.street/CHEDDARandBRIE", - "guest_can_join": false, - "name": "CHEESE", - "num_joined_members": 37, - "room_id": "!ol19s:bleecker.street", - "topic": "Tasty tasty cheese", - "world_readable": true, - "join_rule": "public", - "room_type": "m.space" - } - ], - "next_batch": "p190q", - "prev_batch": "p1902", - "total_room_count_estimate": 115 - } + $ref: ../client-server/definitions/public_rooms_response.yaml servers: - url: "{protocol}://{hostname}{basePath}" variables: diff --git a/data/api/server-server/space_hierarchy.yaml b/data/api/server-server/space_hierarchy.yaml index 8394588b..adc2c3e5 100644 --- a/data/api/server-server/space_hierarchy.yaml +++ b/data/api/server-server/space_hierarchy.yaml @@ -61,22 +61,10 @@ paths: room: description: A summary of the room requested. allOf: - - $ref: ../client-server/definitions/public_rooms_chunk.yaml + - $ref: ../client-server/definitions/room_summary.yaml - type: object title: SpaceHierarchyParentRoom properties: - room_type: - type: string - description: The `type` of room (from - [`m.room.create`](/client-server-api/#mroomcreate)), - if any. - allowed_room_ids: - type: array - items: - type: string - description: |- - If the room is a [restricted room](/server-server-api/#restricted-rooms), these are the room IDs which - are specified by the join rules. Empty or omitted otherwise. children_state: type: array description: |- @@ -96,6 +84,12 @@ paths: description: The `origin_server_ts` for the event. required: - origin_server_ts + room_type: + x-addedInMatrixVersion: "1.4" # Extends room_summary.yaml + encryption: + x-addedInMatrixVersion: "1.15" # Extends room_summary.yaml + room_version: + x-addedInMatrixVersion: "1.15" # Extends room_summary.yaml required: - children_state children: @@ -105,22 +99,16 @@ paths: be excluded. items: allOf: - - $ref: ../client-server/definitions/public_rooms_chunk.yaml + - $ref: ../client-server/definitions/room_summary.yaml - type: object title: SpaceHierarchyChildRoomsChunk properties: room_type: - type: string - description: The `type` of room (from - [`m.room.create`](/client-server-api/#mroomcreate)), - if any. - allowed_room_ids: - type: array - items: - type: string - description: |- - If the room is a [restricted room](/server-server-api/#restricted-rooms), these are the room IDs which - are specified by the join rules. Empty or omitted otherwise. + x-addedInMatrixVersion: "1.4" # Extends room_summary.yaml + encryption: + x-addedInMatrixVersion: "1.15" # Extends room_summary.yaml + room_version: + x-addedInMatrixVersion: "1.15" # Extends room_summary.yaml inaccessible_children: type: array items: diff --git a/data/api/server-server/third_party_invite.yaml b/data/api/server-server/third_party_invite.yaml index 76a7d2ff..019aa2d4 100644 --- a/data/api/server-server/third_party_invite.yaml +++ b/data/api/server-server/third_party_invite.yaml @@ -35,6 +35,8 @@ paths: example: "!abc123:matrix.org" schema: type: string + format: mx-room-id + pattern: "^!" requestBody: content: application/json: @@ -50,16 +52,22 @@ paths: description: |- The room ID the event is for. Must match the ID given in the path. + format: mx-room-id + pattern: "^!" example: "!abc123:matrix.org" sender: type: string description: |- The user ID of the user who sent the original `m.room.third_party_invite` event. + format: mx-user-id + pattern: "^@" example: "@joe:matrix.org" state_key: type: string description: The user ID of the invited user + format: mx-user-id + pattern: "^@" example: "@someone:example.org" content: type: object @@ -82,45 +90,7 @@ paths: third-party identifier. example: alice signed: - type: object - description: |- - A block of content which has been signed, which servers can use to - verify the event. - title: Invite Signatures - properties: - signatures: - type: object - title: Signatures - additionalProperties: - type: object - additionalProperties: - type: string - description: |- - The server signatures for this event. - - The signature is calculated using the process - described at [Signing JSON](/appendices/#signing-json). - example: - magic.forest: - ed25519:3: fQpGIW1Snz+pwLZu6sTy2aHy/DYWWTspTJRPyNp0PKkymfIsNffysMl6ObMMFdIJhk6g6pwlIqZ54rxo8SLmAg - mxid: - type: string - description: The invited matrix user ID - example: "@alice:localhost" - token: - type: string - description: The token used to verify the event - example: abc123 - required: - - signatures - - mxid - - token - example: - mxid: "@alice:localhost" - token: abc123 - signatures: - magic.forest: - ed25519:3: fQpGIW1Snz+pwLZu6sTy2aHy/DYWWTspTJRPyNp0PKkymfIsNffysMl6ObMMFdIJhk6g6pwlIqZ54rxo8SLmAg + $ref: ../../event-schemas/schema/components/signed_third_party_invite.yaml required: - display_name - signed @@ -215,6 +185,8 @@ paths: mxid: type: string description: The user that is now bound to the third-party identifier. + format: mx-user-id + pattern: "^@" example: "@alice:matrix.org" invites: type: array @@ -237,59 +209,23 @@ paths: mxid: type: string description: The now-bound user ID that received the invite. + format: mx-user-id + pattern: "^@" example: "@alice:matrix.org" room_id: type: string description: The room ID the invite is valid for. + format: mx-room-id + pattern: "^!" example: "!somewhere:example.org" sender: type: string description: The user ID that sent the invite. + format: mx-user-id + pattern: "^@" example: "@bob:matrix.org" - # TODO (TravisR): Make this reusable when doing IS spec changes - # also make sure it isn't lying about anything, like the key version signed: - type: object - title: Identity Server Signatures - description: |- - Signature from the identity server using a long-term private - key. - properties: - mxid: - type: string - description: |- - The user ID that has been bound to the third-party - identifier. - example: "@alice:matrix.org" - token: - type: string - # TODO: What is this actually? - description: A token. - example: Hello World - signatures: - type: object - title: Identity Server Signature - description: |- - The signature from the identity server. The `string` key - is the identity server's domain name, such as vector.im - additionalProperties: - type: object - title: Identity Server Domain Signature - description: The signature for the identity server. - properties: - ed25519:0: - type: string - description: The signature. - example: SomeSignatureGoesHere - required: - - ed25519:0 - example: - vector.im: - ed25519:0: SomeSignatureGoesHere - required: - - mxid - - token - - signatures + $ref: ../../event-schemas/schema/components/signed_third_party_invite.yaml required: - medium - address diff --git a/data/api/server-server/wellknown.yaml b/data/api/server-server/wellknown.yaml index 717462f9..920bc356 100644 --- a/data/api/server-server/wellknown.yaml +++ b/data/api/server-server/wellknown.yaml @@ -24,6 +24,12 @@ paths: Gets information about the delegated server for server-server communication between Matrix homeservers. Servers should follow 30x redirects, carefully avoiding redirect loops, and use normal X.509 certificate validation. + + {{% boxes/note %}} + This endpoint should be accessed with the hostname of the homeserver's + [server name](/appendices/#server-name) by making a + GET request to `https://hostname/.well-known/matrix/server`. + {{% /boxes/note %}} operationId: getWellKnown responses: "200": diff --git a/data/event-schemas/examples/m.room.member.yaml b/data/event-schemas/examples/m.room.member.yaml index 5147b258..85e9a9eb 100644 --- a/data/event-schemas/examples/m.room.member.yaml +++ b/data/event-schemas/examples/m.room.member.yaml @@ -1,6 +1,7 @@ { "$ref": "core/state_event.json", "state_key": "@alice:example.org", + "sender": "@alice:example.org", "type": "m.room.member", "content": { "membership": "join", diff --git a/data/event-schemas/examples/m.room.topic.yaml b/data/event-schemas/examples/m.room.topic.yaml index 69e5d4f1..993145a6 100644 --- a/data/event-schemas/examples/m.room.topic.yaml +++ b/data/event-schemas/examples/m.room.topic.yaml @@ -3,6 +3,14 @@ "type": "m.room.topic", "state_key": "", "content": { - "topic": "A room topic" + "m.topic": { + "m.text": [ { + "mimetype": "text/html", + "body": "An interesting room topic" + }, { + "body": "An interesting room topic" + }] + }, + "topic": "An interesting room topic" } } diff --git a/data/event-schemas/schema/components/m_text_content_block.yaml b/data/event-schemas/schema/components/m_text_content_block.yaml new file mode 100644 index 00000000..29c8d148 --- /dev/null +++ b/data/event-schemas/schema/components/m_text_content_block.yaml @@ -0,0 +1,28 @@ +type: array +description: |- + An ordered array of textual representations in different mimetypes. + + Senders SHOULD specify at least one representation and SHOULD always + include a plaintext representation. + + Receivers SHOULD use the first representation in the array that + they understand. +title: TextContentBlock +items: + type: object + title: TextualRepresentation + properties: + mimetype: + type: string + description: The mimetype. Defaults to `text/plain` if omitted. + example: "text/html" + body: + type: string + description: |- + The string content. + + Clients SHOULD validate and sanitize the content as they do + for rich content associated with [`msgtype`](/client-server-api/#mroommessage-msgtypes) + of [`m.room.message`](/client-server-api/#mroommessage). + required: + - body diff --git a/data/event-schemas/schema/components/signed_third_party_invite.yaml b/data/event-schemas/schema/components/signed_third_party_invite.yaml new file mode 100644 index 00000000..b231c978 --- /dev/null +++ b/data/event-schemas/schema/components/signed_third_party_invite.yaml @@ -0,0 +1,45 @@ +title: SignedThirdPartyInvite +description: |- + A block of content which has been signed by the identity server, which + homeservers can use to verify the event. Clients should ignore this. +type: object +properties: + mxid: + description: |- + The user ID that has been bound to the third-party identifier. + type: string + format: mx-user-id + pattern: "^@" + example: "@alice:example.org" + signatures: + title: IdentityServerSignatures + description: |- + The identity server signatures for this block. This is a map of identity + server name to signing key identifier to base64-encoded signature. + + The signatures are calculated using the process described at + [Signing JSON](/appendices/#signing-json). + type: object + additionalProperties: + type: object + additionalProperties: + type: string + example: { + "magic.forest": { + "ed25519:3": "fQpGIW1Snz+pwLZu6sTy2aHy/DYWWTspTJRPyNp0PKkymfIsNffysMl6ObMMFdIJhk6g6pwlIqZ54rxo8SLmAg" + } + } + token: + description: |- + The token generated by the identity server at the + [`/store_invite`](/identity-service-api/#post_matrixidentityv2store-invite) + endpoint. + + It matches the `state_key` of the corresponding [`m.room.third_party_invite`](/client-server-api/#mroomthird_party_invite) + event. + type: string + example: "abc123" +required: + - mxid + - signatures + - token diff --git a/data/event-schemas/schema/core-event-schema/room_event.yaml b/data/event-schemas/schema/core-event-schema/room_event.yaml index 9b8300c1..2df24e61 100644 --- a/data/event-schemas/schema/core-event-schema/room_event.yaml +++ b/data/event-schemas/schema/core-event-schema/room_event.yaml @@ -1,15 +1,15 @@ $schema: https://json-schema.org/draft/2020-12/schema +title: RoomEvent +description: Room Events have the following fields. allOf: - $ref: sync_room_event.yaml -description: Room Events have the following fields. -properties: - room_id: - description: |- - The ID of the room associated with this event. Will not be present on events - that arrive through `/sync`, despite being required everywhere else. - type: string -required: -- room_id -title: RoomEvent -type: object +- type: object + properties: + room_id: + description: |- + The ID of the room associated with this event. Will not be present on events + that arrive through `/sync`, despite being required everywhere else. + type: string + required: + - room_id diff --git a/data/event-schemas/schema/core-event-schema/sync_room_event.yaml b/data/event-schemas/schema/core-event-schema/sync_room_event.yaml index 14f19583..2e3c0f65 100644 --- a/data/event-schemas/schema/core-event-schema/sync_room_event.yaml +++ b/data/event-schemas/schema/core-event-schema/sync_room_event.yaml @@ -19,27 +19,27 @@ $schema: https://json-schema.org/draft/2020-12/schema -allOf: -- $ref: event.yaml +title: SyncRoomEvent description: In addition to the Event fields, Room Events have the following additional fields. -properties: - event_id: - description: The globally unique event identifier. - type: string - sender: - description: Contains the fully-qualified ID of the user who sent this event. - type: string - origin_server_ts: - description: Timestamp in milliseconds on originating homeserver - when this event was sent. - type: integer - format: int64 - unsigned: - $ref: unsigned_prop.yaml -required: -- event_id -- sender -- origin_server_ts -title: SyncRoomEvent -type: object +allOf: +- $ref: event.yaml +- type: object + properties: + event_id: + description: The globally unique event identifier. + type: string + sender: + description: Contains the fully-qualified ID of the user who sent this event. + type: string + origin_server_ts: + description: Timestamp in milliseconds on originating homeserver + when this event was sent. + type: integer + format: int64 + unsigned: + $ref: unsigned_prop.yaml + required: + - event_id + - sender + - origin_server_ts diff --git a/data/event-schemas/schema/core-event-schema/sync_state_event.yaml b/data/event-schemas/schema/core-event-schema/sync_state_event.yaml index 7f500fa5..8b03d61e 100644 --- a/data/event-schemas/schema/core-event-schema/sync_state_event.yaml +++ b/data/event-schemas/schema/core-event-schema/sync_state_event.yaml @@ -16,21 +16,21 @@ $schema: https://json-schema.org/draft/2020-12/schema -allOf: -- $ref: sync_room_event.yaml +title: SyncStateEvent description: In addition to the Room Event fields, State Events have the following additional fields. -properties: - state_key: - description: A unique key which defines the overwriting semantics for this piece - of room state. This value is often a zero-length string. The presence of this - key makes this event a State Event. +allOf: +- $ref: sync_room_event.yaml +- type: object + properties: + state_key: + description: A unique key which defines the overwriting semantics for this piece + of room state. This value is often a zero-length string. The presence of this + key makes this event a State Event. - State keys starting with an `@` are reserved for referencing user IDs, such - as room members. With the exception of a few events, state events set with a - given user's ID as the state key MUST only be set by that user. - type: string -required: -- state_key -title: SyncStateEvent -type: object + State keys starting with an `@` are reserved for referencing user IDs, such + as room members. With the exception of a few events, state events set with a + given user's ID as the state key MUST only be set by that user. + type: string + required: + - state_key diff --git a/data/event-schemas/schema/m.room.member.yaml b/data/event-schemas/schema/m.room.member.yaml index 7bdad88a..e768c906 100644 --- a/data/event-schemas/schema/m.room.member.yaml +++ b/data/event-schemas/schema/m.room.member.yaml @@ -4,17 +4,27 @@ $schema: https://json-schema.org/draft/2020-12/schema allOf: - $ref: core-event-schema/state_event.yaml 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. + 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 following membership states are specified: - - `invite` - The user has been invited to join a room, but has not yet joined it. They may not participate in the room until they join. - - `join` - The user has joined the room (possibly after accepting an invite), and may participate in it. - - `leave` - The user was once joined to the room, but has since left (possibly by choice, or possibly by being kicked). - - `ban` - The user has been banned from the room, and is no longer allowed to join it until they are un-banned from the room (by having their membership state set to a value other than `ban`). - - `knock` - The user has knocked on the room, requesting permission to participate. They may not participate in the room until they join. + - `invite` - The user has been invited to join a room, but has not yet joined it. They may not + participate in the room until they join. + - `join` - The user has joined the room (possibly after accepting an invite), and may participate + in it. + - `leave` - The user was once joined to the room, but has since left (possibly by choice, or + possibly by being kicked). + - `ban` - The user has been banned from the room, and is no longer allowed to join it until they + are un-banned from the room (by having their membership state set to a value other than `ban`). + - `knock` - The user has knocked on the room, requesting permission to participate. They may not + participate in the room until they join. - The `third_party_invite` property will be set if this invite is an `invite` event and is the successor of an `m.room.third_party_invite` event, and absent otherwise. + The `third_party_invite` property will be set if this invite is an `invite` event and is the + successor of an [`m.room.third_party_invite`](/client-server-api/#mroomthird_party_invite) event, + and absent otherwise. This event may also include an `invite_room_state` key inside the event's `unsigned` data. If present, this contains an array of [stripped state events](/client-server-api/#stripped-state) @@ -59,63 +69,54 @@ properties: - ban type: string is_direct: - description: Flag indicating if the room containing this event was created with the intention of being a direct chat. See [Direct Messaging](/client-server-api/#direct-messaging). + description: |- + Flag indicating if the room containing this event was created with the intention of being + a direct chat. See [Direct Messaging](/client-server-api/#direct-messaging). type: boolean join_authorised_via_users_server: x-addedInMatrixVersion: "1.2" type: string description: |- - Usually found on `join` events, this field is used to denote which homeserver (through representation of a user with sufficient power level) - authorised the user's join. More information about this field can be found in the [Restricted Rooms Specification](/client-server-api/#restricted-rooms). + Usually found on `join` events, this field is used to denote which homeserver (through + representation of a user with sufficient power level) authorised the user's join. More + information about this field can be found in the [Restricted Rooms Specification](/client-server-api/#restricted-rooms). - Client and server implementations should be aware of the [signing implications](/rooms/v8/#authorization-rules) of including this - field in further events: in particular, the event must be signed by the server which - owns the user ID in the field. When copying the membership event's `content` - (for profile updates and similar) it is therefore encouraged to exclude this - field in the copy, as otherwise the event might fail event authorization. + Client and server implementations should be aware of the [signing implications](/rooms/v8/#authorization-rules) + of including this field in further events: in particular, the event must be signed by the + server which owns the user ID in the field. When copying the membership event's `content` + (for profile updates and similar) it is therefore encouraged to exclude this field in the + copy, as otherwise the event might fail event authorization. reason: x-addedInMatrixVersion: "1.1" type: string description: |- - Optional user-supplied text for why their membership has changed. For kicks and bans, this is typically the reason for the kick or ban. - For other membership changes, this is a way for the user to communicate their intent without having to send a message to the room, such - as in a case where Bob rejects an invite from Alice about an upcoming concert, but can't make it that day. + Optional user-supplied text for why their membership has changed. For kicks and bans, + this is typically the reason for the kick or ban. For other membership changes, this is a + way for the user to communicate their intent without having to send a message to the + room, such as in a case where Bob rejects an invite from Alice about an upcoming concert, + but can't make it that day. - Clients are not recommended to show this reason to users when receiving an invite due to the potential for spam and abuse. Hiding the - reason behind a button or other component is recommended. + Clients are not recommended to show this reason to users when receiving an invite due to + the potential for spam and abuse. Hiding the reason behind a button or other component is + recommended. third_party_invite: + title: ThirdPartyInvite + description: |- + A third-party invite, if this `m.room.member` is the successor to an + [`m.room.third_party_invite`](/client-server-api/#mroomthird_party_invite) + event. + type: object properties: display_name: - description: A name which can be displayed to represent the user instead of their third-party identifier + description: |- + A name which can be displayed to represent the user instead of their + third-party identifier type: string signed: - description: 'A block of content which has been signed, which servers can use to verify the event. Clients should ignore this.' - properties: - mxid: - description: The invited matrix user ID. Must be equal to the user_id property of the event. - type: string - signatures: - description: 'A single signature from the verifying server, in the format specified by the Signing Events section of the server-server API.' - title: Signatures - type: object - additionalProperties: - type: object - additionalProperties: - type: string - token: - description: The token property of the containing third_party_invite object. - type: string - required: - - mxid - - signatures - - token - title: signed - type: object + $ref: components/signed_third_party_invite.yaml required: - display_name - signed - title: Invite - type: object required: - membership title: EventContent diff --git a/data/event-schemas/schema/m.room.message$m.audio.yaml b/data/event-schemas/schema/m.room.message$m.audio.yaml index 289bafe6..a1116c59 100644 --- a/data/event-schemas/schema/m.room.message$m.audio.yaml +++ b/data/event-schemas/schema/m.room.message$m.audio.yaml @@ -17,8 +17,8 @@ properties: "1.10": This property can act as a caption for the audio. format: description: |- - The format used in the `formatted_body`. Currently only - `org.matrix.custom.html` is supported. + The format used in the `formatted_body`. This is required if `formatted_body` + is specified. Currently only `org.matrix.custom.html` is supported. type: string x-addedInMatrixVersion: "1.10" formatted_body: diff --git a/data/event-schemas/schema/m.room.message$m.emote.yaml b/data/event-schemas/schema/m.room.message$m.emote.yaml index 1d3d8d6f..a6f18763 100644 --- a/data/event-schemas/schema/m.room.message$m.emote.yaml +++ b/data/event-schemas/schema/m.room.message$m.emote.yaml @@ -16,8 +16,8 @@ properties: type: string format: description: |- - The format used in the `formatted_body`. Currently only - `org.matrix.custom.html` is supported. + The format used in the `formatted_body`. This is required if `formatted_body` + is specified. Currently only `org.matrix.custom.html` is supported. type: string formatted_body: description: |- diff --git a/data/event-schemas/schema/m.room.message$m.file.yaml b/data/event-schemas/schema/m.room.message$m.file.yaml index 0615122c..9fef045d 100644 --- a/data/event-schemas/schema/m.room.message$m.file.yaml +++ b/data/event-schemas/schema/m.room.message$m.file.yaml @@ -17,8 +17,8 @@ properties: "1.10": This property can act as a caption for the file. format: description: |- - The format used in the `formatted_body`. Currently only - `org.matrix.custom.html` is supported. + The format used in the `formatted_body`. This is required if `formatted_body` + is specified. Currently only `org.matrix.custom.html` is supported. type: string x-addedInMatrixVersion: "1.10" formatted_body: diff --git a/data/event-schemas/schema/m.room.message$m.image.yaml b/data/event-schemas/schema/m.room.message$m.image.yaml index ac7e290a..68e44f06 100644 --- a/data/event-schemas/schema/m.room.message$m.image.yaml +++ b/data/event-schemas/schema/m.room.message$m.image.yaml @@ -17,8 +17,8 @@ properties: "1.10": This property can act as a caption for the image. format: description: |- - The format used in the `formatted_body`. Currently only - `org.matrix.custom.html` is supported. + The format used in the `formatted_body`. This is required if `formatted_body` + is specified. Currently only `org.matrix.custom.html` is supported. type: string x-addedInMatrixVersion: "1.10" formatted_body: diff --git a/data/event-schemas/schema/m.room.message$m.key.verification.request.yaml b/data/event-schemas/schema/m.room.message$m.key.verification.request.yaml index 2c6fdd43..9fc7e276 100644 --- a/data/event-schemas/schema/m.room.message$m.key.verification.request.yaml +++ b/data/event-schemas/schema/m.room.message$m.key.verification.request.yaml @@ -24,8 +24,8 @@ properties: verification. format: description: |- - The format used in the `formatted_body`. Currently only - `org.matrix.custom.html` is supported. + The format used in the `formatted_body`. This is required if `formatted_body` + is specified. Currently only `org.matrix.custom.html` is supported. type: string formatted_body: description: |- diff --git a/data/event-schemas/schema/m.room.message$m.notice.yaml b/data/event-schemas/schema/m.room.message$m.notice.yaml index 31c9a224..5ca72d98 100644 --- a/data/event-schemas/schema/m.room.message$m.notice.yaml +++ b/data/event-schemas/schema/m.room.message$m.notice.yaml @@ -16,8 +16,8 @@ properties: type: string format: description: |- - The format used in the `formatted_body`. Currently only - `org.matrix.custom.html` is supported. + The format used in the `formatted_body`. This is required if `formatted_body` + is specified. Currently only `org.matrix.custom.html` is supported. type: string formatted_body: description: |- diff --git a/data/event-schemas/schema/m.room.message$m.text.yaml b/data/event-schemas/schema/m.room.message$m.text.yaml index 5d47a311..e0279e9e 100644 --- a/data/event-schemas/schema/m.room.message$m.text.yaml +++ b/data/event-schemas/schema/m.room.message$m.text.yaml @@ -16,8 +16,8 @@ properties: type: string format: description: |- - The format used in the `formatted_body`. Currently only - `org.matrix.custom.html` is supported. + The format used in the `formatted_body`. This is required if `formatted_body` + is specified. Currently only `org.matrix.custom.html` is supported. type: string formatted_body: description: |- diff --git a/data/event-schemas/schema/m.room.message$m.video.yaml b/data/event-schemas/schema/m.room.message$m.video.yaml index 7a0c0f21..6d8c49e8 100644 --- a/data/event-schemas/schema/m.room.message$m.video.yaml +++ b/data/event-schemas/schema/m.room.message$m.video.yaml @@ -17,8 +17,8 @@ properties: "1.10": This property can act as a caption for the video. format: description: |- - The format used in the `formatted_body`. Currently only - `org.matrix.custom.html` is supported. + The format used in the `formatted_body`. This is required if `formatted_body` + is specified. Currently only `org.matrix.custom.html` is supported. type: string x-addedInMatrixVersion: "1.10" formatted_body: diff --git a/data/event-schemas/schema/m.room.third_party_invite.yaml b/data/event-schemas/schema/m.room.third_party_invite.yaml index b00a1466..704e0290 100644 --- a/data/event-schemas/schema/m.room.third_party_invite.yaml +++ b/data/event-schemas/schema/m.room.third_party_invite.yaml @@ -3,28 +3,56 @@ $schema: https://json-schema.org/draft/2020-12/schema allOf: - $ref: core-event-schema/state_event.yaml -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." +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. properties: content: properties: display_name: - 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." + 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. type: string key_validity_url: - 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'." + 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'. type: string + format: uri public_key: - description: A base64-encoded ed25519 key with which token must be signed (though a signature from any entry in public_keys is also sufficient). This exists for backwards compatibility. + description: |- + An Ed25519 key with which the token must be signed (though a signature + from any entry in `public_keys` is also sufficient). + + The key is encoded using [Unpadded Base64](/appendices/#unpadded-base64), + using the standard or URL-safe alphabets. + + This exists for backwards compatibility. type: string public_keys: description: Keys with which the token may be signed. items: properties: key_validity_url: - description: "An optional 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'. If this URL is absent, the key must be considered valid indefinitely." + description: |- + An optional URL which can be fetched, with querystring + `public_key=`, to validate whether the key has been + revoked. The URL must return a JSON object containing a boolean + property named `valid`. If this URL is absent, the key must be + considered valid indefinitely. type: string public_key: - description: A base-64 encoded ed25519 key with which token may be signed. + description: |- + An Ed25519 key with which the token may be signed. + + The key is encoded using [Unpadded Base64](/appendices/#unpadded-base64), + using the standard or URL-safe alphabets. type: string required: - public_key @@ -37,11 +65,15 @@ properties: - public_key type: object state_key: - description: 'The token, of which a signature must be produced in order to join the room.' + description: |- + The token, of which a signature must be produced in order to join the + room. type: string type: enum: - m.room.third_party_invite type: string -title: 'An invitation to a room issued to a third-party identifier, rather than a matrix user ID.' +title: |- + An invitation to a room issued to a third-party identifier, rather than a + matrix user ID. type: object diff --git a/data/event-schemas/schema/m.room.topic.yaml b/data/event-schemas/schema/m.room.topic.yaml index df41b5ae..a50a710e 100644 --- a/data/event-schemas/schema/m.room.topic.yaml +++ b/data/event-schemas/schema/m.room.topic.yaml @@ -3,13 +3,41 @@ $schema: https://json-schema.org/draft/2020-12/schema allOf: - $ref: core-event-schema/state_event.yaml -description: 'A topic is a short message detailing what is currently being discussed in the room. It can also be used as a way to display extra information about the room, which may not be suitable for the room name. The room topic can also be set when creating a room using `/createRoom` with the `topic` key.' +description: |- + A topic is a short message detailing what is currently being discussed + in the room. It can also be used as a way to display extra information + about the room, which may not be suitable for the room name. The room + topic can also be set when creating a room using + [`/createRoom`](client-server-api/#post_matrixclientv3createroom), either + with the `topic` key or by specifying a full event in `initial_state`. + + If the `topic` property is absent, null, or empty then the topic is unset. In other words, + an empty `topic` property effectively resets the room to having no topic. + + In order to prevent formatting abuse in room topics, clients SHOULD + limit the length of topics during both entry and display, for instance, + by capping the number of displayed lines. Additionally, clients SHOULD + ignore things like headings and enumerations (or format them as regular + text). properties: content: properties: topic: - description: The topic text. + description: |- + The topic in plain text. + + This SHOULD duplicate the content of the `text/plain` + representation in `m.topic` if any exists. type: string + m.topic: + type: object + title: TopicContentBlock + x-addedInMatrixVersion: "1.15" + description: |- + Textual representation of the room topic in different mimetypes. + properties: + m.text: + $ref: components/m_text_content_block.yaml required: - topic type: object diff --git a/data/schemas/oauth2-client-metadata.yaml b/data/schemas/oauth2-client-metadata.yaml new file mode 100644 index 00000000..ba12f3f9 --- /dev/null +++ b/data/schemas/oauth2-client-metadata.yaml @@ -0,0 +1,140 @@ +# Copyright 2025 The Matrix.org Foundation C.I.C. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +title: OAuthClientMetadata +type: object +description: |- + This definition of the metadata specifies only the fields that are meaningful + in the context of the Matrix specification. All the possible values are + registered in the [OAuth Dynamic Client Registration Metadata registry](https://www.iana.org/assignments/oauth-parameters/oauth-parameters.xhtml#client-metadata), + and normative definitions of them are available in their respective RFCs. +properties: + client_uri: + type: string + format: uri + description: |- + A URL to a valid web page that SHOULD give the user more information about + the client. + + This URL MUST use the `https` scheme and SHOULD NOT require authentication + to access. It MUST NOT use a user or password in the authority component + of the URI. + + The server MAY reject client registrations if this field is invalid or + missing. + + This URI is a common base for all the other URIs in the metadata: those + MUST be either on the same host or on a subdomain of the host of the + `client_uri`. The port number, path and query components MAY be different. + + For example, if the `client_uri` is `https://example.com/`, then one of + the `redirect_uris` can be `https://example.com/callback` or + `https://app.example.com/callback`, but not `https://app.com/callback`. + client_name: + type: string + description: |- + Human-readable name of the client to be presented to the user. + + This field can be [localized](/client-server-api/#metadata-localization). + logo_uri: + type: string + format: uri + description: |- + URL that references a logo for the client. + + This URL MUST use the `https` scheme. + + This field can be [localized](/client-server-api/#metadata-localization). + tos_uri: + type: string + format: uri + description: |- + URL that points to a human-readable terms of service document for the + client. + + This URL MUST use the `https` scheme and SHOULD NOT require authentication + to access. It MUST NOT use a user or password in the authority component + of the URI. + + If this field is set, the server SHOULD show or link to this URL. + + This field can be [localized](/client-server-api/#metadata-localization). + policy_uri: + type: string + format: uri + description: |- + URL that points to a human-readable policy document for the client. + + This URL MUST use the `https` scheme and SHOULD NOT require authentication + to access. It MUST NOT use a user or password in the authority component + of the URI. + + If this field is set, the server SHOULD show or link to this URL. + + This field can be [localized](/client-server-api/#metadata-localization). + redirect_uris: + type: array + description: |- + Array of redirection URIs for use in redirect-based flows. + + At least one URI is required to use the authorization code grant. + + The server MUST perform [validation on redirect URIs](/client-server-api/#redirect-uri-validation). + items: + type: string + format: uri + description: A redirection URI. + response_types: + type: array + description: |- + Array of the OAuth 2.0 response types that the client may use. + + This MUST include the `code` value to use the authorization code grant. + + The server MUST ignore values that it does not understand. + items: + type: string + description: A response type that the client may use. + grant_types: + type: array + description: |- + Array of the OAuth 2.0 grant types that the client may use. + + This MUST include: + - the `authorization_code` value to use the authorization code grant, + - the `refresh_token` value to use the refresh token grant. + + The server MUST ignore values that it does not understand. + items: + type: string + description: A grant type that the client may use. + token_endpoint_auth_method: + type: string + description: |- + String indicator of the requested authentication method for the token + endpoint. + + The homeserver MUST support the `none` value, as most Matrix clients are + client-side only, do not have a server component, and therefore are public + clients. + application_type: + type: string + description: |- + Kind of the application. + + The homeserver MUST support the `web` and `native` values to be able to + perform [redirect URI validation](/client-server-api/#redirect-uri-validation). + + Defaults to `web` if omitted. +required: + - client_uri diff --git a/data/string-formats.yaml b/data/string-formats.yaml index 352d21fc..fca325b9 100644 --- a/data/string-formats.yaml +++ b/data/string-formats.yaml @@ -51,6 +51,11 @@ mx-room-id: url: appendices#room-ids # regex: "^!" +mx-room-alias: + title: Room Alias + url: appendices#room-aliases + # regex: "^#" + mx-server-name: title: Server Name url: appendices#server-name diff --git a/meta/documentation_style.rst b/meta/documentation_style.rst index 1844f6ec..afb5ff98 100644 --- a/meta/documentation_style.rst +++ b/meta/documentation_style.rst @@ -60,6 +60,11 @@ General you have at home. "identity server" is clear, whereas "identityserver" is horrible. +* When talking about a user's "display name", it is spelt as two words. In + identifiers such as within the content of an ``m.room.member`` event, it is + spelt as a single word, ``displayname``. (There are some historical exceptions + to this where the identifier is spelt ``display_name``.) + * Lists should: * Be introduced with a colon. @@ -84,12 +89,12 @@ Changes between spec versions Sections should reference the Matrix spec version they were added/changed in. This is often a guess at what the next version will be - please use the currently released version with a minor version bump as the referenced version. For example, if the -current version is `v1.1` then annotate your changes with `v1.2`. +current version is ``v1.1`` then annotate your changes with ``v1.2``. "Added/changed in" tags can be documented as the following: -* `{{% added-in v="1.2" %}}` or `{{% changed-in v="1.2" %}}` within Markdown documents. -* `x-addedInMatrixVersion` and `x-changedInMatrixVersion` within OpenAPI. +* ``{{% added-in v="1.2" %}}`` or ``{{% changed-in v="1.2" %}}`` within Markdown documents. +* ``x-addedInMatrixVersion`` and ``x-changedInMatrixVersion`` within OpenAPI. OpenAPI ~~~~~~~ @@ -185,4 +190,4 @@ Describing grammar Use `RFC5234-style ABNF `_ when describing the grammar for something in the spec, such as user IDs or server names. Use lowercase -and underscore-delimited element names (`user_id`, not `UserID` or `user-id`). +and underscore-delimited element names (``user_id``, not ``UserID`` or ``user-id``). diff --git a/scripts/dump-openapi.py b/scripts/dump-openapi.py index 490ac9bf..49ad9147 100755 --- a/scripts/dump-openapi.py +++ b/scripts/dump-openapi.py @@ -32,6 +32,35 @@ import yaml scripts_dir = os.path.dirname(os.path.abspath(__file__)) api_dir = os.path.join(os.path.dirname(scripts_dir), "data", "api") +# Finds a Hugo shortcode in a string. +# +# A shortcode is defined as (newlines and whitespaces for presentation purpose): +# +# {{% +# +# +# (optional ) +# +# %}} +# +# With: +# +# * : any word character and `-` and `/`. `re.ASCII` is used to only match +# ASCII characters in the name. +# * : any character except `}`, must not start or end with a +# whitespace. +shortcode_regex = re.compile(r"""\{\{\% # {{% + \s* # zero or more whitespaces + (?P[\w/-]+) # name of shortcode + (?:\s+(?P[^\s\}][^\}]+[^\s\}]))? # optional list of parameters + \s* # zero or more whitespaces + \%\}\} # %}}""", re.ASCII | re.VERBOSE) + +# Parses the parameters of a Hugo shortcode. +# +# For simplicity, this currently only supports the `key="value"` format. +shortcode_params_regex = re.compile(r"(?P\w+)=\"(?P[^\"]+)\"", re.ASCII) + def prefix_absolute_path_references(text, base_url): """Adds base_url to absolute-path references. @@ -44,17 +73,90 @@ def prefix_absolute_path_references(text, base_url): """ return text.replace("](/", "]({}/".format(base_url)) -def edit_links(node, base_url): - """Finds description nodes and makes any links in them absolute.""" +def replace_match(match, replacement): + """Replaces the regex match by the replacement in the text.""" + return match.string[:match.start()] + replacement + match.string[match.end():] + +def replace_shortcode(shortcode): + """Replaces the shortcode by a Markdown fallback in the text. + + The supported shortcodes are: + + * boxes/note, boxes/rationale, boxes/warning + * added-in, changed-in + + All closing tags (`{{ /shortcode }}`) are replaced with the empty string. + """ + + if shortcode['name'].startswith("/"): + # This is the end of the shortcode, just remove it. + return replace_match(shortcode, "") + + # Parse the parameters of the shortcode + params = {} + if shortcode['params']: + for param in shortcode_params_regex.finditer(shortcode['params']): + if param['key']: + params[param['key']] = param['value'] + + match shortcode['name']: + case "boxes/note": + return replace_match(shortcode, "**NOTE:** ") + case "boxes/rationale": + return replace_match(shortcode, "**RATIONALE:** ") + case "boxes/warning": + return replace_match(shortcode, "**WARNING:** ") + case "added-in": + version = params['v'] + if not version: + raise ValueError("Missing parameter `v` for `added-in` shortcode") + + return replace_match(shortcode, f"**[Added in `v{version}`]** ") + case "changed-in": + version = params['v'] + if not version: + raise ValueError("Missing parameter `v` for `changed-in` shortcode") + + return replace_match(shortcode, f"**[Changed in `v{version}`]** ") + case _: + raise ValueError("Unknown shortcode", shortcode['name']) + + +def find_and_replace_shortcodes(text): + """Finds Hugo shortcodes and replaces them by a Markdown fallback. + + The supported shortcodes are: + + * boxes/note, boxes/rationale, boxes/warning + * added-in, changed-in + """ + # We use a `while` loop with `search` instead of a `for` loop with + # `finditer`, because as soon as we start replacing text, the + # indices of the match are invalid. + while shortcode := shortcode_regex.search(text): + text = replace_shortcode(shortcode) + + return text + +def edit_descriptions(node, base_url): + """Finds description nodes and apply fixes to them. + + The fixes that are applied are: + + * Make links absolute + * Replace Hugo shortcodes + """ if isinstance(node, dict): for key in node: if isinstance(node[key], str): node[key] = prefix_absolute_path_references(node[key], base_url) + node[key] = find_and_replace_shortcodes(node[key]) else: - edit_links(node[key], base_url) + edit_descriptions(node[key], base_url) elif isinstance(node, list): for item in node: - edit_links(item, base_url) + edit_descriptions(item, base_url) + parser = argparse.ArgumentParser( "dump-openapi.py - assemble the OpenAPI specs into a single JSON file" @@ -164,7 +266,7 @@ for filename in os.listdir(selected_api_dir): if untagged != 0: print("{} untagged operations, you may want to look into fixing that.".format(untagged)) -edit_links(output, base_url) +edit_descriptions(output, base_url) print("Generating %s" % output_file)