From ab64bda76dcefcff2fcf4e050427409b9d0068af Mon Sep 17 00:00:00 2001 From: Will Date: Wed, 20 Jan 2021 10:48:15 -0800 Subject: [PATCH] Add syntax highlighting --- content/appendices.md | 418 +++++++------ content/client-server-api/_index.md | 565 ++++++++++-------- .../modules/end_to_end_encryption.md | 166 ++--- .../modules/instant_messaging.md | 28 +- content/client-server-api/modules/mentions.md | 14 +- content/client-server-api/modules/push.md | 514 ++++++++-------- content/client-server-api/modules/receipts.md | 16 +- content/client-server-api/modules/secrets.md | 126 ++-- .../modules/send_to_device.md | 32 +- content/identity-service-api.md | 10 +- content/server-server-api.md | 152 ++--- 11 files changed, 1118 insertions(+), 923 deletions(-) diff --git a/content/appendices.md b/content/appendices.md index 3a27abbb..17155583 100644 --- a/content/appendices.md +++ b/content/appendices.md @@ -87,19 +87,21 @@ Note Float values are not permitted by this encoding. - import json - - def canonical_json(value): - return json.dumps( - value, - # Encode code-points outside of ASCII as UTF-8 rather than \u escapes - ensure_ascii=False, - # Remove unnecessary white space. - separators=(',',':'), - # Sort the keys of dictionaries. - sort_keys=True, - # Encode the resulting Unicode as UTF-8 bytes. - ).encode("UTF-8") +```py +import json + +def canonical_json(value): + return json.dumps( + value, + # Encode code-points outside of ASCII as UTF-8 rather than \u escapes + ensure_ascii=False, + # Remove unnecessary white space. + separators=(',',':'), + # Sort the keys of dictionaries. + sort_keys=True, + # Encode the resulting Unicode as UTF-8 bytes. + ).encode("UTF-8") +``` #### Grammar @@ -138,108 +140,144 @@ transformation code. Given the following JSON object: - {} +```json +{} +``` The following canonical JSON should be produced: - {} +```json +{} +``` Given the following JSON object: - { - "one": 1, - "two": "Two" - } +```json +{ + "one": 1, + "two": "Two" +} +``` The following canonical JSON should be produced: - {"one":1,"two":"Two"} +```json +{"one":1,"two":"Two"} +``` Given the following JSON object: - { - "b": "2", - "a": "1" - } +```json +{ + "b": "2", + "a": "1" +} +``` The following canonical JSON should be produced: - {"a":"1","b":"2"} +```json +{"a":"1","b":"2"} +``` Given the following JSON object: - {"b":"2","a":"1"} +```json +{"b":"2","a":"1"} +``` The following canonical JSON should be produced: - {"a":"1","b":"2"} +```json +{"a":"1","b":"2"} +``` Given the following JSON object: - { - "auth": { - "success": true, - "mxid": "@john.doe:example.com", - "profile": { - "display_name": "John Doe", - "three_pids": [ - { - "medium": "email", - "address": "john.doe@example.org" - }, - { - "medium": "msisdn", - "address": "123456789" - } - ] - } +```json +{ + "auth": { + "success": true, + "mxid": "@john.doe:example.com", + "profile": { + "display_name": "John Doe", + "three_pids": [ + { + "medium": "email", + "address": "john.doe@example.org" + }, + { + "medium": "msisdn", + "address": "123456789" + } + ] } } +} +``` The following canonical JSON should be produced: - {"auth":{"mxid":"@john.doe:example.com","profile":{"display_name":"John Doe","three_pids":[{"address":"john.doe@example.org","medium":"email"},{"address":"123456789","medium":"msisdn"}]},"success":true}} +```json +{"auth":{"mxid":"@john.doe:example.com","profile":{"display_name":"John Doe","three_pids":[{"address":"john.doe@example.org","medium":"email"},{"address":"123456789","medium":"msisdn"}]},"success":true}} +``` Given the following JSON object: - { - "a": "日本語" - } +```json +{ + "a": "日本語" +} +``` The following canonical JSON should be produced: - {"a":"日本語"} +```json +{"a":"日本語"} +``` Given the following JSON object: - { - "本": 2, - "日": 1 - } +```json +{ + "本": 2, + "日": 1 +} +``` The following canonical JSON should be produced: - {"日":1,"本":2} +```json +{"日":1,"本":2} +``` Given the following JSON object: - { - "a": "\u65E5" - } +```json +{ + "a": "\u65E5" +} +``` The following canonical JSON should be produced: - {"a":"日"} +```json +{"a":"日"} +``` Given the following JSON object: - { - "a": null - } +```json +{ + "a": null +} +``` The following canonical JSON should be produced: - {"a":null} +```json +{"a":null} +``` ### Signing Details @@ -263,36 +301,40 @@ The `unsigned` object and the `signatures` object are not covered by the signature. Therefore intermediate entities can add unsigned data such as timestamps and additional signatures. - { - "name": "example.org", - "signing_keys": { - "ed25519:1": "XSl0kuyvrXNj6A+7/tkrB9sxSbRi08Of5uRhxOqZtEQ" - }, - "unsigned": { - "age_ts": 922834800000 - }, - "signatures": { - "example.org": { - "ed25519:1": "s76RUgajp8w172am0zQb/iPTHsRnb4SkrzGoeCOSFfcBY2V/1c8QfrmdXHpvnc2jK5BD1WiJIxiMW95fMjK7Bw" - } - } - } - - def sign_json(json_object, signing_key, signing_name): - signatures = json_object.pop("signatures", {}) - unsigned = json_object.pop("unsigned", None) - - signed = signing_key.sign(encode_canonical_json(json_object)) - signature_base64 = encode_base64(signed.signature) - - key_id = "%s:%s" % (signing_key.alg, signing_key.version) - signatures.setdefault(signing_name, {})[key_id] = signature_base64 - - json_object["signatures"] = signatures - if unsigned is not None: - json_object["unsigned"] = unsigned - - return json_object +```json +{ + "name": "example.org", + "signing_keys": { + "ed25519:1": "XSl0kuyvrXNj6A+7/tkrB9sxSbRi08Of5uRhxOqZtEQ" + }, + "unsigned": { + "age_ts": 922834800000 + }, + "signatures": { + "example.org": { + "ed25519:1": "s76RUgajp8w172am0zQb/iPTHsRnb4SkrzGoeCOSFfcBY2V/1c8QfrmdXHpvnc2jK5BD1WiJIxiMW95fMjK7Bw" + } + } +} +``` + +```py +def sign_json(json_object, signing_key, signing_name): + signatures = json_object.pop("signatures", {}) + unsigned = json_object.pop("unsigned", None) + + signed = signing_key.sign(encode_canonical_json(json_object)) + signature_base64 = encode_base64(signed.signature) + + key_id = "%s:%s" % (signing_key.alg, signing_key.version) + signatures.setdefault(signing_name, {})[key_id] = signature_base64 + + json_object["signatures"] = signatures + if unsigned is not None: + json_object["unsigned"] = unsigned + + return json_object +``` ### Checking for a Signature @@ -872,122 +914,138 @@ In each case, the server name and key ID are as follows: Given an empty JSON object: - {} +```json +{} +``` The JSON signing algorithm should emit the following signed data: - { - "signatures": { - "domain": { - "ed25519:1": "K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ" - } +```json +{ + "signatures": { + "domain": { + "ed25519:1": "K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ" } } +} +``` Given the following JSON object with data values in it: - { - "one": 1, - "two": "Two" - } +```json +{ + "one": 1, + "two": "Two" +} +``` The JSON signing algorithm should emit the following signed JSON: - { - "one": 1, - "signatures": { - "domain": { - "ed25519:1": "KqmLSbO39/Bzb0QIYE82zqLwsA+PDzYIpIRA2sRQ4sL53+sN6/fpNSoqE7BP7vBZhG6kYdD13EIMJpvhJI+6Bw" - } - }, - "two": "Two" - } +```json +{ + "one": 1, + "signatures": { + "domain": { + "ed25519:1": "KqmLSbO39/Bzb0QIYE82zqLwsA+PDzYIpIRA2sRQ4sL53+sN6/fpNSoqE7BP7vBZhG6kYdD13EIMJpvhJI+6Bw" + } + }, + "two": "Two" +} +``` ### Event Signing Given the following minimally-sized event: - { - "room_id": "!x:domain", - "sender": "@a:domain", - "origin": "domain", - "origin_server_ts": 1000000, - "signatures": {}, - "hashes": {}, - "type": "X", - "content": {}, - "prev_events": [], - "auth_events": [], - "depth": 3, - "unsigned": { - "age_ts": 1000000 - } +```json +{ + "room_id": "!x:domain", + "sender": "@a:domain", + "origin": "domain", + "origin_server_ts": 1000000, + "signatures": {}, + "hashes": {}, + "type": "X", + "content": {}, + "prev_events": [], + "auth_events": [], + "depth": 3, + "unsigned": { + "age_ts": 1000000 } +} +``` The event signing algorithm should emit the following signed event: - { - "auth_events": [], - "content": {}, - "depth": 3, - "hashes": { - "sha256": "5jM4wQpv6lnBo7CLIghJuHdW+s2CMBJPUOGOC89ncos" - }, - "origin": "domain", - "origin_server_ts": 1000000, - "prev_events": [], - "room_id": "!x:domain", - "sender": "@a:domain", - "signatures": { - "domain": { - "ed25519:1": "KxwGjPSDEtvnFgU00fwFz+l6d2pJM6XBIaMEn81SXPTRl16AqLAYqfIReFGZlHi5KLjAWbOoMszkwsQma+lYAg" - } - }, - "type": "X", - "unsigned": { - "age_ts": 1000000 +```json +{ + "auth_events": [], + "content": {}, + "depth": 3, + "hashes": { + "sha256": "5jM4wQpv6lnBo7CLIghJuHdW+s2CMBJPUOGOC89ncos" + }, + "origin": "domain", + "origin_server_ts": 1000000, + "prev_events": [], + "room_id": "!x:domain", + "sender": "@a:domain", + "signatures": { + "domain": { + "ed25519:1": "KxwGjPSDEtvnFgU00fwFz+l6d2pJM6XBIaMEn81SXPTRl16AqLAYqfIReFGZlHi5KLjAWbOoMszkwsQma+lYAg" } + }, + "type": "X", + "unsigned": { + "age_ts": 1000000 } +} +``` Given the following event containing redactable content: - { - "content": { - "body": "Here is the message content" - }, - "event_id": "$0:domain", - "origin": "domain", - "origin_server_ts": 1000000, - "type": "m.room.message", - "room_id": "!r:domain", - "sender": "@u:domain", - "signatures": {}, - "unsigned": { - "age_ts": 1000000 - } +```json +{ + "content": { + "body": "Here is the message content" + }, + "event_id": "$0:domain", + "origin": "domain", + "origin_server_ts": 1000000, + "type": "m.room.message", + "room_id": "!r:domain", + "sender": "@u:domain", + "signatures": {}, + "unsigned": { + "age_ts": 1000000 } +} +``` The event signing algorithm should emit the following signed event: - { - "content": { - "body": "Here is the message content" - }, - "event_id": "$0:domain", - "hashes": { - "sha256": "onLKD1bGljeBWQhWZ1kaP9SorVmRQNdN5aM2JYU2n/g" - }, - "origin": "domain", - "origin_server_ts": 1000000, - "type": "m.room.message", - "room_id": "!r:domain", - "sender": "@u:domain", - "signatures": { - "domain": { - "ed25519:1": "Wm+VzmOUOz08Ds+0NTWb1d4CZrVsJSikkeRxh6aCcUwu6pNC78FunoD7KNWzqFn241eYHYMGCA5McEiVPdhzBA" - } - }, - "unsigned": { - "age_ts": 1000000 +```json +{ + "content": { + "body": "Here is the message content" + }, + "event_id": "$0:domain", + "hashes": { + "sha256": "onLKD1bGljeBWQhWZ1kaP9SorVmRQNdN5aM2JYU2n/g" + }, + "origin": "domain", + "origin_server_ts": 1000000, + "type": "m.room.message", + "room_id": "!r:domain", + "sender": "@u:domain", + "signatures": { + "domain": { + "ed25519:1": "Wm+VzmOUOz08Ds+0NTWb1d4CZrVsJSikkeRxh6aCcUwu6pNC78FunoD7KNWzqFn241eYHYMGCA5McEiVPdhzBA" } + }, + "unsigned": { + "age_ts": 1000000 } +} +``` diff --git a/content/client-server-api/_index.md b/content/client-server-api/_index.md index e3bec2f1..be244c97 100644 --- a/content/client-server-api/_index.md +++ b/content/client-server-api/_index.md @@ -71,10 +71,12 @@ inconsistency. Any errors which occur at the Matrix API level MUST return a "standard error response". This is a JSON object which looks like: - { - "errcode": "", - "error": "" - } +```json +{ + "errcode": "", + "error": "" +} +``` The `error` string will be a human-readable error message, usually a sentence explaining what went wrong. The `errcode` string will be a @@ -439,22 +441,24 @@ homeserver returns an HTTP 401 response, with a JSON body, as follows: HTTP/1.1 401 Unauthorized Content-Type: application/json +```json +{ + "flows": [ { - "flows": [ - { - "stages": [ "example.type.foo", "example.type.bar" ] - }, - { - "stages": [ "example.type.foo", "example.type.baz" ] - } - ], - "params": { - "example.type.baz": { - "example_key": "foobar" - } - }, - "session": "xxxxxx" + "stages": [ "example.type.foo", "example.type.bar" ] + }, + { + "stages": [ "example.type.foo", "example.type.baz" ] } + ], + "params": { + "example.type.baz": { + "example_key": "foobar" + } + }, + "session": "xxxxxx" +} +``` In addition to the `flows`, this object contains some extra information: @@ -482,15 +486,17 @@ type `example.type.foo`, it might submit something like this: POST /_matrix/client/r0/endpoint HTTP/1.1 Content-Type: application/json - { - "a_request_parameter": "something", - "another_request_parameter": "something else", - "auth": { - "type": "example.type.foo", - "session": "xxxxxx", - "example_credential": "verypoorsharedsecret" - } - } +```json +{ + "a_request_parameter": "something", + "another_request_parameter": "something else", + "auth": { + "type": "example.type.foo", + "session": "xxxxxx", + "example_credential": "verypoorsharedsecret" + } +} +``` If the homeserver deems the authentication attempt to be successful but still requires more stages to be completed, it returns HTTP status 401 @@ -501,23 +507,25 @@ client has completed successfully: HTTP/1.1 401 Unauthorized Content-Type: application/json +```json +{ + "completed": [ "example.type.foo" ], + "flows": [ { - "completed": [ "example.type.foo" ], - "flows": [ - { - "stages": [ "example.type.foo", "example.type.bar" ] - }, - { - "stages": [ "example.type.foo", "example.type.baz" ] - } - ], - "params": { - "example.type.baz": { - "example_key": "foobar" - } - }, - "session": "xxxxxx" + "stages": [ "example.type.foo", "example.type.bar" ] + }, + { + "stages": [ "example.type.foo", "example.type.baz" ] } + ], + "params": { + "example.type.baz": { + "example_key": "foobar" + } + }, + "session": "xxxxxx" +} +``` Individual stages may require more than one request to complete, in which case the response will be as if the request was unauthenticated @@ -531,25 +539,27 @@ status 401 response as above, with the addition of the standard HTTP/1.1 401 Unauthorized Content-Type: application/json +```json +{ + "errcode": "M_FORBIDDEN", + "error": "Invalid password", + "completed": [ "example.type.foo" ], + "flows": [ { - "errcode": "M_FORBIDDEN", - "error": "Invalid password", - "completed": [ "example.type.foo" ], - "flows": [ - { - "stages": [ "example.type.foo", "example.type.bar" ] - }, - { - "stages": [ "example.type.foo", "example.type.baz" ] - } - ], - "params": { - "example.type.baz": { - "example_key": "foobar" - } - }, - "session": "xxxxxx" + "stages": [ "example.type.foo", "example.type.bar" ] + }, + { + "stages": [ "example.type.foo", "example.type.baz" ] } + ], + "params": { + "example.type.baz": { + "example_key": "foobar" + } + }, + "session": "xxxxxx" +} +``` If the request fails for a reason other than authentication, the server returns an error message in the standard format. For example: @@ -557,10 +567,12 @@ returns an error message in the standard format. For example: HTTP/1.1 400 Bad request Content-Type: application/json - { - "errcode": "M_EXAMPLE_ERROR", - "error": "Something was wrong" - } +```json +{ + "errcode": "M_EXAMPLE_ERROR", + "error": "Something was wrong" +} +``` If the client has completed all stages of a flow, the homeserver performs the API call and returns the result as normal. Completed stages @@ -641,14 +653,16 @@ plain-text. To use this authentication type, clients should submit an auth dict as follows: - { - "type": "m.login.password", - "identifier": { - ... - }, - "password": "", - "session": "" - } +``` +{ + "type": "m.login.password", + "identifier": { + ... + }, + "password": "", + "session": "" +} +``` where the `identifier` property is a user identifier object, as described in [Identifier types](#identifier-types). @@ -656,30 +670,34 @@ described in [Identifier types](#identifier-types). For example, to authenticate using the user's Matrix ID, clients would submit: - { - "type": "m.login.password", - "identifier": { - "type": "m.id.user", - "user": "" - }, - "password": "", - "session": "" - } +```json +{ + "type": "m.login.password", + "identifier": { + "type": "m.id.user", + "user": "" + }, + "password": "", + "session": "" +} +``` Alternatively reply using a 3PID bound to the user's account on the homeserver using the `/account/3pid`\_ API rather than giving the `user` explicitly as follows: - { - "type": "m.login.password", - "identifier": { - "type": "m.id.thirdparty", - "medium": "", - "address": "" - }, - "password": "", - "session": "" - } +```json +{ + "type": "m.login.password", + "identifier": { + "type": "m.id.thirdparty", + "medium": "", + "address": "" + }, + "password": "", + "session": "" +} +``` In the case that the homeserver does not know about the supplied 3PID, the homeserver must respond with 403 Forbidden. @@ -695,11 +713,13 @@ The user completes a Google ReCaptcha 2.0 challenge To use this authentication type, clients should submit an auth dict as follows: - { - "type": "m.login.recaptcha", - "response": "", - "session": "" - } +```json +{ + "type": "m.login.recaptcha", + "response": "", + "session": "" +} +``` #### Single Sign-On @@ -730,18 +750,20 @@ information should be submitted to the homeserver. To use this authentication type, clients should submit an auth dict as follows: +```json +{ + "type": "m.login.email.identity", + "threepidCreds": [ { - "type": "m.login.email.identity", - "threepidCreds": [ - { - "sid": "", - "client_secret": "", - "id_server": "", - "id_access_token": "" - } - ], - "session": "" + "sid": "", + "client_secret": "", + "id_server": "", + "id_access_token": "" } + ], + "session": "" +} +``` Note that `id_server` (and therefore `id_access_token`) is optional if the `/requestToken` request did not include them. @@ -762,18 +784,20 @@ information should be submitted to the homeserver. To use this authentication type, clients should submit an auth dict as follows: +```json +{ + "type": "m.login.msisdn", + "threepidCreds": [ { - "type": "m.login.msisdn", - "threepidCreds": [ - { - "sid": "", - "client_secret": "", - "id_server": "", - "id_access_token": "" - } - ], - "session": "" + "sid": "", + "client_secret": "", + "id_server": "", + "id_access_token": "" } + ], + "session": "" +} +``` Note that `id_server` (and therefore `id_access_token`) is optional if the `/requestToken` request did not include them. @@ -798,10 +822,12 @@ server can instead send flows `m.login.recaptcha, m.login.dummy` and To use this authentication type, clients should submit an auth dict with just the type and session, if provided: - { - "type": "m.login.dummy", - "session": "" - } +```json +{ + "type": "m.login.dummy", + "session": "" +} +``` ##### Fallback @@ -820,11 +846,13 @@ This MUST return an HTML page which can perform this authentication stage. This page must use the following JavaScript when the authentication has been completed: - if (window.onAuthDone) { - window.onAuthDone(); - } else if (window.opener && window.opener.postMessage) { - window.opener.postMessage("authDone", "*"); - } +```js +if (window.onAuthDone) { + window.onAuthDone(); +} else if (window.opener && window.opener.postMessage) { + window.opener.postMessage("authDone", "*"); +} +``` This allows the client to either arrange for the global function `onAuthDone` to be defined in an embedded browser, or to use the HTML5 @@ -836,64 +864,67 @@ Once a client receives the notification that the authentication stage has been completed, it should resubmit the request with an auth dict with just the session ID: - { - "session": "" - } +```json +{ + "session": "" +} +``` #### Example A client webapp might use the following JavaScript to open a popup window which will handle unknown login types: - /** - * Arguments: - * homeserverUrl: the base url of the homeserver (e.g. "https://matrix.org") - * - * apiEndpoint: the API endpoint being used (e.g. - * "/_matrix/client/%CLIENT_MAJOR_VERSION%/account/password") - * - * loginType: the loginType being attempted (e.g. "m.login.recaptcha") - * - * sessionID: the session ID given by the homeserver in earlier requests - * - * onComplete: a callback which will be called with the results of the request - */ - function unknownLoginType(homeserverUrl, apiEndpoint, loginType, sessionID, onComplete) { - var popupWindow; - - var eventListener = function(ev) { - // check it's the right message from the right place. - if (ev.data !== "authDone" || ev.origin !== homeserverUrl) { - return; - } - - // close the popup - popupWindow.close(); - window.removeEventListener("message", eventListener); - - // repeat the request - var requestBody = { - auth: { - session: sessionID, - }, - }; - - request({ - method:'POST', url:apiEndpoint, json:requestBody, - }, onComplete); +```js +/** + * Arguments: + * homeserverUrl: the base url of the homeserver (e.g. "https://matrix.org") + * + * apiEndpoint: the API endpoint being used (e.g. + * "/_matrix/client/%CLIENT_MAJOR_VERSION%/account/password") + * + * loginType: the loginType being attempted (e.g. "m.login.recaptcha") + * + * sessionID: the session ID given by the homeserver in earlier requests + * + * onComplete: a callback which will be called with the results of the request + */ +function unknownLoginType(homeserverUrl, apiEndpoint, loginType, sessionID, onComplete) { + var popupWindow; + + var eventListener = function(ev) { + // check it's the right message from the right place. + if (ev.data !== "authDone" || ev.origin !== homeserverUrl) { + return; + } + + // close the popup + popupWindow.close(); + window.removeEventListener("message", eventListener); + + // repeat the request + var requestBody = { + auth: { + session: sessionID, + }, }; - window.addEventListener("message", eventListener); + request({ + method:'POST', url:apiEndpoint, json:requestBody, + }, onComplete); + }; - var url = homeserverUrl + - "/_matrix/client/%CLIENT_MAJOR_VERSION%/auth/" + - encodeURIComponent(loginType) + - "/fallback/web?session=" + - encodeURIComponent(sessionID); + window.addEventListener("message", eventListener); + var url = homeserverUrl + + "/_matrix/client/%CLIENT_MAJOR_VERSION%/auth/" + + encodeURIComponent(loginType) + + "/fallback/web?session=" + + encodeURIComponent(sessionID); - popupWindow = window.open(url); - } + popupWindow = window.open(url); +} +``` ##### Identifier types @@ -920,10 +951,12 @@ A client can identify a user using their Matrix ID. This can either be the fully qualified Matrix user ID, or just the localpart of the user ID. - "identifier": { - "type": "m.id.user", - "user": "" - } +```json +"identifier": { + "type": "m.id.user", + "user": "" +} +``` #### Third-party ID @@ -940,11 +973,13 @@ using the `/account/3pid`\_ API. See the [3PID Types](../appendices.html#pid-types) Appendix for a list of Third-party ID media. - "identifier": { - "type": "m.id.thirdparty", - "medium": "", - "address": "" - } +```json +"identifier": { + "type": "m.id.thirdparty", + "medium": "", + "address": "" +} +``` #### Phone number @@ -962,11 +997,13 @@ If the client wishes to canonicalise the phone number, then it can use the `m.id.thirdparty` identifier type with a `medium` of `msisdn` instead. - "identifier": { - "type": "m.id.phone", - "country": "", - "phone": "" - } +```json +"identifier": { + "type": "m.id.phone", + "country": "", + "phone": "" +} +``` 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 @@ -983,27 +1020,31 @@ API](#user-interactive-authentication-api). For a simple username/password login, clients should submit a `/login` request as follows: - { - "type": "m.login.password", - "identifier": { - "type": "m.id.user", - "user": "" - }, - "password": "" - } +```json +{ + "type": "m.login.password", + "identifier": { + "type": "m.id.user", + "user": "" + }, + "password": "" +} +``` Alternatively, a client can use a 3PID bound to the user's account on the homeserver using the `/account/3pid`\_ API rather than giving the `user` explicitly, as follows: - { - "type": "m.login.password", - "identifier": { - "medium": "", - "address": "" - }, - "password": "" - } +```json +{ + "type": "m.login.password", + "identifier": { + "medium": "", + "address": "" + }, + "password": "" +} +``` In the case that the homeserver does not know about the supplied 3PID, the homeserver must respond with `403 Forbidden`. @@ -1011,10 +1052,12 @@ the homeserver must respond with `403 Forbidden`. To log in using a login token, clients should submit a `/login` request as follows: - { - "type": "m.login.token", - "token": "" - } +```json +{ + "type": "m.login.token", + "token": "" +} +``` As with [token-based]() interactive login, the `token` must encode the user ID. In the case that the token is not valid, the homeserver must @@ -1166,13 +1209,15 @@ to change their password. An example of the capability API's response for this capability is: - { - "capabilities": { - "m.change_password": { - "enabled": false - } - } +```json +{ + "capabilities": { + "m.change_password": { + "enabled": false } + } +} +``` ### `m.room_versions` capability @@ -1183,19 +1228,21 @@ upgrade their rooms. An example of the capability API's response for this capability is: - { - "capabilities": { - "m.room_versions": { - "default": "1", - "available": { - "1": "stable", - "2": "stable", - "3": "unstable", - "custom-version": "unstable" - } - } +```json +{ + "capabilities": { + "m.room_versions": { + "default": "1", + "available": { + "1": "stable", + "2": "stable", + "3": "unstable", + "custom-version": "unstable" } } + } +} +``` This capability mirrors the same restrictions of [room versions](../index.html#room-versions) to describe which versions are @@ -1626,36 +1673,48 @@ There are several APIs provided to `GET` events for a room: Valid requests look like: - PUT /rooms/!roomid:domain/state/m.example.event - { "key" : "without a state key" } - - PUT /rooms/!roomid:domain/state/m.another.example.event/foo - { "key" : "with 'foo' as the state key" } +``` +PUT /rooms/!roomid:domain/state/m.example.event +{ "key" : "without a state key" } +``` +``` +PUT /rooms/!roomid:domain/state/m.another.example.event/foo +{ "key" : "with 'foo' as the state key" } +``` In contrast, these requests are invalid: - POST /rooms/!roomid:domain/state/m.example.event/ - { "key" : "cannot use POST here" } - - PUT /rooms/!roomid:domain/state/m.another.example.event/foo/11 - { "key" : "txnIds are not supported" } +``` +POST /rooms/!roomid:domain/state/m.example.event/ +{ "key" : "cannot use POST here" } +``` +``` +PUT /rooms/!roomid:domain/state/m.another.example.event/foo/11 +{ "key" : "txnIds are not supported" } +``` Care should be taken to avoid setting the wrong `state key`: - PUT /rooms/!roomid:domain/state/m.another.example.event/11 - { "key" : "with '11' as the state key, but was probably intended to be a txnId" } +``` +PUT /rooms/!roomid:domain/state/m.another.example.event/11 +{ "key" : "with '11' as the state key, but was probably intended to be a txnId" } +``` The `state_key` is often used to store state about individual users, by using the user ID as the `state_key` value. For example: - PUT /rooms/!roomid:domain/state/m.favorite.animal.event/%40my_user%3Aexample.org - { "animal" : "cat", "reason": "fluffy" } +``` +PUT /rooms/!roomid:domain/state/m.favorite.animal.event/%40my_user%3Aexample.org +{ "animal" : "cat", "reason": "fluffy" } +``` In some cases, there may be no need for a `state_key`, so it can be omitted: - PUT /rooms/!roomid:domain/state/m.room.bgd.color - { "color": "red", "hex": "#ff0000" } +``` +PUT /rooms/!roomid:domain/state/m.room.bgd.color +{ "color": "red", "hex": "#ff0000" } +``` {{room\_send\_cs\_http\_api}} @@ -1872,19 +1931,23 @@ someone, the user performing the ban MUST have the required power level. To ban a user, a request should be made to `/rooms//ban`\_ with: - { - "user_id": "" - "reason": "string: " - } +```json +{ + "user_id": "", + "reason": "string: " +} +```` Banning a user adjusts the banned member's membership state to `ban`. Like with other membership changes, a user can directly adjust the target member's state, by making a request to `/rooms//state/m.room.member/`: - { - "membership": "ban" - } +```json +{ + "membership": "ban" +} +``` A user must be explicitly unbanned with a request to `/rooms//unban`\_ before they can re-join the room or be @@ -1938,11 +2001,13 @@ Homeservers SHOULD implement rate limiting to reduce the risk of being overloaded. If a request is refused due to rate limiting, it should return a standard error response of the form: - { - "errcode": "M_LIMIT_EXCEEDED", - "error": "string", - "retry_after_ms": integer (optional) - } +```json +{ + "errcode": "M_LIMIT_EXCEEDED", + "error": "string", + "retry_after_ms": integer (optional) +} +``` The `retry_after_ms` key SHOULD be included to tell the client how long they have to wait in milliseconds before they can try again. 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 d0af1e8a..089a6748 100644 --- a/content/client-server-api/modules/end_to_end_encryption.md +++ b/content/client-server-api/modules/end_to_end_encryption.md @@ -99,14 +99,16 @@ with the following properties: Example: - { - "key":"06UzBknVHFMwgi7AVloY7ylC+xhOhEX4PkNge14Grl8", - "signatures": { - "@user:example.com": { - "ed25519:EGURVBUNJP": "YbJva03ihSj5mPk+CHMJKUKlCXCPFXjXOK6VqBnN9nA2evksQcTGn6hwQfrgRHIDDXO2le49x7jnWJHMJrJoBQ" - } - } +```json +{ + "key":"06UzBknVHFMwgi7AVloY7ylC+xhOhEX4PkNge14Grl8", + "signatures": { + "@user:example.com": { + "ed25519:EGURVBUNJP": "YbJva03ihSj5mPk+CHMJKUKlCXCPFXjXOK6VqBnN9nA2evksQcTGn6hwQfrgRHIDDXO2le49x7jnWJHMJrJoBQ" } + } +} +``` ##### Device keys @@ -1239,22 +1241,24 @@ keys in [Server-side key backups](#server-side-key-backups) but adds the Example: - [ - { - "algorithm": "m.megolm.v1.aes-sha2", - "forwarding_curve25519_key_chain": [ - "hPQNcabIABgGnx3/ACv/jmMmiQHoeFfuLB17tzWp6Hw" - ], - "room_id": "!Cuyf34gef24t:localhost", - "sender_key": "RF3s+E7RkTQTGF2d8Deol0FkQvgII2aJDf3/Jp5mxVU", - "sender_claimed_keys": { - "ed25519": "", - }, - "session_id": "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ", - "session_key": "AgAAAADxKHa9uFxcXzwYoNueL5Xqi69IkD4sni8Llf..." +``` +[ + { + "algorithm": "m.megolm.v1.aes-sha2", + "forwarding_curve25519_key_chain": [ + "hPQNcabIABgGnx3/ACv/jmMmiQHoeFfuLB17tzWp6Hw" + ], + "room_id": "!Cuyf34gef24t:localhost", + "sender_key": "RF3s+E7RkTQTGF2d8Deol0FkQvgII2aJDf3/Jp5mxVU", + "sender_claimed_keys": { + "ed25519": "", }, - ... - ] + "session_id": "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ", + "session_key": "AgAAAADxKHa9uFxcXzwYoNueL5Xqi69IkD4sni8Llf..." + }, + ... +] +``` #### Messaging Algorithms @@ -1297,19 +1301,21 @@ device key, and must publish Curve25519 one-time keys. An event encrypted using Olm has the following format: - { - "type": "m.room.encrypted", - "content": { - "algorithm": "m.olm.v1.curve25519-aes-sha2", - "sender_key": "", - "ciphertext": { - "": { - "type": 0, - "body": "" - } - } +```json +{ + "type": "m.room.encrypted", + "content": { + "algorithm": "m.olm.v1.curve25519-aes-sha2", + "sender_key": "", + "ciphertext": { + "": { + "type": 0, + "body": "" } } + } +} +``` `ciphertext` is a mapping from device Curve25519 key to an encrypted payload for that device. `body` is a Base64-encoded Olm message body. @@ -1334,18 +1340,20 @@ message. The plaintext payload is of the form: - { - "type": "", - "content": "", - "sender": "", - "recipient": "", - "recipient_keys": { - "ed25519": "" - }, - "keys": { - "ed25519": "" - } - } +```json +{ + "type": "", + "content": "", + "sender": "", + "recipient": "", + "recipient_keys": { + "ed25519": "" + }, + "keys": { + "ed25519": "" + } +} +``` The type and content of the plaintext message event are given in the payload. @@ -1418,25 +1426,29 @@ Devices that support Megolm must support Olm, and include An event encrypted using Megolm has the following format: - { - "type": "m.room.encrypted", - "content": { - "algorithm": "m.megolm.v1.aes-sha2", - "sender_key": "", - "device_id": "", - "session_id": "", - "ciphertext": "" - } - } +```json +{ + "type": "m.room.encrypted", + "content": { + "algorithm": "m.megolm.v1.aes-sha2", + "sender_key": "", + "device_id": "", + "session_id": "", + "ciphertext": "" + } +} +``` The encrypted payload can contain any message event. The plaintext is of the form: - { - "type": "", - "content": "", - "room_id": "" - } +```json +{ + "type": "", + "content": "", + "room_id": "" +} +``` We include the room ID in the payload, because otherwise the homeserver would be able to change the room a message was sent in. @@ -1554,22 +1566,24 @@ already shared a room. Example response: - { - "next_batch": "s72595_4483_1934", - "rooms": {"leave": {}, "join": {}, "invite": {}}, - "device_lists": { - "changed": [ - "@alice:example.com", - ], - "left": [ - "@bob:example.com", - ], - }, - "device_one_time_keys_count": { - "curve25519": 10, - "signed_curve25519": 20 - } - } +```json +{ + "next_batch": "s72595_4483_1934", + "rooms": {"leave": {}, "join": {}, "invite": {}}, + "device_lists": { + "changed": [ + "@alice:example.com", + ], + "left": [ + "@bob:example.com", + ], + }, + "device_one_time_keys_count": { + "curve25519": 10, + "signed_curve25519": 20 + } +} +``` #### Reporting that decryption keys are withheld diff --git a/content/client-server-api/modules/instant_messaging.md b/content/client-server-api/modules/instant_messaging.md index 76ec9b17..41b8e427 100644 --- a/content/client-server-api/modules/instant_messaging.md +++ b/content/client-server-api/modules/instant_messaging.md @@ -332,21 +332,23 @@ a rich reply, infinitely. An `m.in_reply_to` relationship looks like the following: - { - ... - "type": "m.room.message", - "content": { - "msgtype": "m.text", - "body": "", - "format": "org.matrix.custom.html", - "formatted_body": "", - "m.relates_to": { - "m.in_reply_to": { - "event_id": "$another:event.com" - } - } +``` +{ + ... + "type": "m.room.message", + "content": { + "msgtype": "m.text", + "body": "", + "format": "org.matrix.custom.html", + "formatted_body": "", + "m.relates_to": { + "m.in_reply_to": { + "event_id": "$another:event.com" } } + } +} +``` ####### Fallbacks and event representation diff --git a/content/client-server-api/modules/mentions.md b/content/client-server-api/modules/mentions.md index 9baebe8d..c999d932 100644 --- a/content/client-server-api/modules/mentions.md +++ b/content/client-server-api/modules/mentions.md @@ -18,12 +18,14 @@ Mentions apply only to [m.room.message]() events where the `msgtype` is To make a mention, reference the entity being mentioned in the `formatted_body` using an anchor, like so: - { - "body": "Hello Alice!", - "msgtype": "m.text", - "format": "org.matrix.custom.html", - "formatted_body": "Hello Alice!" - } +```json +{ + "body": "Hello Alice!", + "msgtype": "m.text", + "format": "org.matrix.custom.html", + "formatted_body": "Hello Alice!" +} +``` #### Client behaviour diff --git a/content/client-server-api/modules/push.md b/content/client-server-api/modules/push.md index e3ca6ebf..b035ba0c 100644 --- a/content/client-server-api/modules/push.md +++ b/content/client-server-api/modules/push.md @@ -282,15 +282,17 @@ user. By default this rule is disabled. Definition - { - "rule_id": ".m.rule.master", - "default": true, - "enabled": false, - "conditions": [], - "actions": [ - "dont_notify" - ] - } +```json +{ + "rule_id": ".m.rule.master", + "default": true, + "enabled": false, + "conditions": [], + "actions": [ + "dont_notify" + ] +} +``` ######## `.m.rule.suppress_notices` @@ -298,21 +300,23 @@ Matches messages with a `msgtype` of `notice`. Definition: - { - "rule_id": ".m.rule.suppress_notices", - "default": true, - "enabled": true, - "conditions": [ - { - "kind": "event_match", - "key": "content.msgtype", - "pattern": "m.notice", - } - ], - "actions": [ - "dont_notify", - ] - } +```json +{ + "rule_id": ".m.rule.suppress_notices", + "default": true, + "enabled": true, + "conditions": [ + { + "kind": "event_match", + "key": "content.msgtype", + "pattern": "m.notice", + } + ], + "actions": [ + "dont_notify", + ] +} +``` ######## `.m.rule.invite_for_me` @@ -320,35 +324,37 @@ Matches any invites to a new room for this user. Definition: - { - "rule_id": ".m.rule.invite_for_me", - "default": true, - "enabled": true, - "conditions": [ - { - "key": "type", - "kind": "event_match", - "pattern": "m.room.member" - }, - { - "key": "content.membership", - "kind": "event_match", - "pattern": "invite" - }, - { - "key": "state_key", - "kind": "event_match", - "pattern": "[the user's Matrix ID]" - } - ], - "actions": [ - "notify", - { - "set_tweak": "sound", - "value": "default" - } - ] - } +```json +{ + "rule_id": ".m.rule.invite_for_me", + "default": true, + "enabled": true, + "conditions": [ + { + "key": "type", + "kind": "event_match", + "pattern": "m.room.member" + }, + { + "key": "content.membership", + "kind": "event_match", + "pattern": "invite" + }, + { + "key": "state_key", + "kind": "event_match", + "pattern": "[the user's Matrix ID]" + } + ], + "actions": [ + "notify", + { + "set_tweak": "sound", + "value": "default" + } + ] +} +``` ######## `.m.rule.member_event` @@ -356,21 +362,23 @@ Matches any `m.room.member_event`. Definition: - { - "rule_id": ".m.rule.member_event", - "default": true, - "enabled": true, - "conditions": [ - { - "key": "type", - "kind": "event_match", - "pattern": "m.room.member" - } - ], - "actions": [ - "dont_notify" - ] - } +```json +{ + "rule_id": ".m.rule.member_event", + "default": true, + "enabled": true, + "conditions": [ + { + "key": "type", + "kind": "event_match", + "pattern": "m.room.member" + } + ], + "actions": [ + "dont_notify" + ] +} +``` ######## `.m.rule.contains_display_name` @@ -379,26 +387,28 @@ current display name in the room in which it was sent. Definition: - { - "rule_id": ".m.rule.contains_display_name", - "default": true, - "enabled": true, - "conditions": [ - { - "kind": "contains_display_name" - } - ], - "actions": [ - "notify", - { - "set_tweak": "sound", - "value": "default" - }, - { - "set_tweak": "highlight" - } - ] - } +```json +{ + "rule_id": ".m.rule.contains_display_name", + "default": true, + "enabled": true, + "conditions": [ + { + "kind": "contains_display_name" + } + ], + "actions": [ + "notify", + { + "set_tweak": "sound", + "value": "default" + }, + { + "set_tweak": "highlight" + } + ] +} +``` ######## `.m.rule.tombstone` @@ -408,29 +418,31 @@ an `@room` notification would accomplish. Definition: - { - "rule_id": ".m.rule.tombstone", - "default": true, - "enabled": true, - "conditions": [ - { - "kind": "event_match", - "key": "type", - "pattern": "m.room.tombstone" - }, - { - "kind": "event_match", - "key": "state_key", - "pattern": "" - } - ], - "actions": [ - "notify", - { - "set_tweak": "highlight" - } - ] - } +```json +{ + "rule_id": ".m.rule.tombstone", + "default": true, + "enabled": true, + "conditions": [ + { + "kind": "event_match", + "key": "type", + "pattern": "m.room.tombstone" + }, + { + "kind": "event_match", + "key": "state_key", + "pattern": "" + } + ], + "actions": [ + "notify", + { + "set_tweak": "highlight" + } + ] +} +``` ######## `.m.rule.roomnotif` @@ -439,28 +451,30 @@ Matches any message whose content is unencrypted and contains the text Definition: - { - "rule_id": ".m.rule.roomnotif", - "default": true, - "enabled": true, - "conditions": [ - { - "kind": "event_match", - "key": "content.body", - "pattern": "@room" - }, - { - "kind": "sender_notification_permission", - "key": "room" - } - ], - "actions": [ - "notify", - { - "set_tweak": "highlight" - } - ] - } +```json +{ + "rule_id": ".m.rule.roomnotif", + "default": true, + "enabled": true, + "conditions": [ + { + "kind": "event_match", + "key": "content.body", + "pattern": "@room" + }, + { + "kind": "sender_notification_permission", + "key": "room" + } + ], + "actions": [ + "notify", + { + "set_tweak": "highlight" + } + ] +} +``` ####### Default Content Rules @@ -471,22 +485,24 @@ part of the user's Matrix ID, separated by word boundaries. Definition (as a `content` rule): - { - "rule_id": ".m.rule.contains_user_name", - "default": true, - "enabled": true, - "pattern": "[the local part of the user's Matrix ID]", - "actions": [ - "notify", - { - "set_tweak": "sound", - "value": "default" - }, - { - "set_tweak": "highlight" - } - ] - } +```json +{ + "rule_id": ".m.rule.contains_user_name", + "default": true, + "enabled": true, + "pattern": "[the local part of the user's Matrix ID]", + "actions": [ + "notify", + { + "set_tweak": "sound", + "value": "default" + }, + { + "set_tweak": "highlight" + } + ] +} +``` ####### Default Underride Rules @@ -496,25 +512,27 @@ Matches any incoming VOIP call. Definition: - { - "rule_id": ".m.rule.call", - "default": true, - "enabled": true, - "conditions": [ - { - "key": "type", - "kind": "event_match", - "pattern": "m.call.invite" - } - ], - "actions": [ - "notify", - { - "set_tweak": "sound", - "value": "ring" - } - ] - } +```json +{ + "rule_id": ".m.rule.call", + "default": true, + "enabled": true, + "conditions": [ + { + "key": "type", + "kind": "event_match", + "pattern": "m.call.invite" + } + ], + "actions": [ + "notify", + { + "set_tweak": "sound", + "value": "ring" + } + ] +} +``` ######## `.m.rule.encrypted_room_one_to_one` @@ -526,29 +544,31 @@ encrypted (in 1:1 rooms) or none. Definition: - { - "rule_id": ".m.rule.encrypted_room_one_to_one", - "default": true, - "enabled": true, - "conditions": [ - { - "kind": "room_member_count", - "is": "2" - }, - { - "kind": "event_match", - "key": "type", - "pattern": "m.room.encrypted" - } - ], - "actions": [ - "notify", - { - "set_tweak": "sound", - "value": "default" - } - ] - } +```json +{ + "rule_id": ".m.rule.encrypted_room_one_to_one", + "default": true, + "enabled": true, + "conditions": [ + { + "kind": "room_member_count", + "is": "2" + }, + { + "kind": "event_match", + "key": "type", + "pattern": "m.room.encrypted" + } + ], + "actions": [ + "notify", + { + "set_tweak": "sound", + "value": "default" + } + ] +} +``` ######## `.m.rule.room_one_to_one` @@ -556,29 +576,31 @@ Matches any message sent in a room with exactly two members. Definition: - { - "rule_id": ".m.rule.room_one_to_one", - "default": true, - "enabled": true, - "conditions": [ - { - "kind": "room_member_count", - "is": "2" - }, - { - "kind": "event_match", - "key": "type", - "pattern": "m.room.message" - } - ], - "actions": [ - "notify", - { - "set_tweak": "sound", - "value": "default" - } - ] - } +```json +{ + "rule_id": ".m.rule.room_one_to_one", + "default": true, + "enabled": true, + "conditions": [ + { + "kind": "room_member_count", + "is": "2" + }, + { + "kind": "event_match", + "key": "type", + "pattern": "m.room.message" + } + ], + "actions": [ + "notify", + { + "set_tweak": "sound", + "value": "default" + } + ] +} +``` ######## `.m.rule.message` @@ -586,21 +608,23 @@ Matches all chat messages. Definition: - { - "rule_id": ".m.rule.message", - "default": true, - "enabled": true, - "conditions": [ - { - "kind": "event_match", - "key": "type", - "pattern": "m.room.message" - } - ], - "actions": [ - "notify" - ] - } +```json +{ + "rule_id": ".m.rule.message", + "default": true, + "enabled": true, + "conditions": [ + { + "kind": "event_match", + "key": "type", + "pattern": "m.room.message" + } + ], + "actions": [ + "notify" + ] +} +``` ######## `.m.rule.encrypted` @@ -611,21 +635,23 @@ either matches *all* events that are encrypted (in group rooms) or none. Definition: - { - "rule_id": ".m.rule.encrypted", - "default": true, - "enabled": true, - "conditions": [ - { - "kind": "event_match", - "key": "type", - "pattern": "m.room.encrypted" - } - ], - "actions": [ - "notify" - ] - } +```json +{ + "rule_id": ".m.rule.encrypted", + "default": true, + "enabled": true, + "conditions": [ + { + "kind": "event_match", + "key": "type", + "pattern": "m.room.encrypted" + } + ], + "actions": [ + "notify" + ] +} +``` ##### Push Rules: API diff --git a/content/client-server-api/modules/receipts.md b/content/client-server-api/modules/receipts.md index 485ac0fc..423decea 100644 --- a/content/client-server-api/modules/receipts.md +++ b/content/client-server-api/modules/receipts.md @@ -68,15 +68,17 @@ before delivering them to clients. Receipts are sent across federation as EDUs with type `m.receipt`. The format of the EDUs are: - { - : { - : { - : { } - }, - ... +``` +{ + : { + : { + : { } }, ... - } + }, + ... +} +``` These are always sent as deltas to previously sent receipts. Currently only a single `` should be used: `m.read`. diff --git a/content/client-server-api/modules/secrets.md b/content/client-server-api/modules/secrets.md index 7a337f42..d65512e1 100644 --- a/content/client-server-api/modules/secrets.md +++ b/content/client-server-api/modules/secrets.md @@ -114,37 +114,43 @@ Some secret is encrypted using keys with ID `key_id_1` and `key_id_2`: `org.example.some.secret`: - { - "encrypted": { - "key_id_1": { - "ciphertext": "base64+encoded+encrypted+data", - "mac": "base64+encoded+mac", - // ... other properties according to algorithm property in - // m.secret_storage.key.key_id_1 - }, - "key_id_2": { - // ... - } - } +``` +{ + "encrypted": { + "key_id_1": { + "ciphertext": "base64+encoded+encrypted+data", + "mac": "base64+encoded+mac", + // ... other properties according to algorithm property in + // m.secret_storage.key.key_id_1 + }, + "key_id_2": { + // ... } + } +} +``` and the key descriptions for the keys would be: `m.secret_storage.key.key_id_1`: - { - "name": "Some key", - "algorithm": "m.secret_storage.v1.aes-hmac-sha2", - // ... other properties according to algorithm - } +``` +{ + "name": "Some key", + "algorithm": "m.secret_storage.v1.aes-hmac-sha2", + // ... other properties according to algorithm +} +``` `m.secret_storage.key.key_id_2`: - { - "name": "Some other key", - "algorithm": "m.secret_storage.v1.aes-hmac-sha2", - // ... other properties according to algorithm - } +``` +{ + "name": "Some other key", + "algorithm": "m.secret_storage.v1.aes-hmac-sha2", + // ... other properties according to algorithm +} +``` ###### `m.secret_storage.v1.aes-hmac-sha2` @@ -247,24 +253,28 @@ correctly entered the key, clients should: For example, the `m.secret_storage.key.key_id` for a key using this algorithm could look like: - { - "name": "m.default", - "algorithm": "m.secret_storage.v1.aes-hmac-sha2", - "iv": "random+data", - "mac": "mac+of+encrypted+zeros" - } +```json +{ + "name": "m.default", + "algorithm": "m.secret_storage.v1.aes-hmac-sha2", + "iv": "random+data", + "mac": "mac+of+encrypted+zeros" +} +``` and data encrypted using this algorithm could look like this: - { - "encrypted": { - "key_id": { - "iv": "16+bytes+base64", - "ciphertext": "base64+encoded+encrypted+data", - "mac": "base64+encoded+mac" - } +```json +{ + "encrypted": { + "key_id": { + "iv": "16+bytes+base64", + "ciphertext": "base64+encoded+encrypted+data", + "mac": "base64+encoded+mac" } - } + } +} +``` ###### Key representation @@ -339,15 +349,17 @@ in the `iterations` parameter. Example: - { - "passphrase": { - "algorithm": "m.pbkdf2", - "salt": "MmMsAlty", - "iterations": 100000, - "bits": 256 - }, - ... - } +``` +{ + "passphrase": { + "algorithm": "m.pbkdf2", + "salt": "MmMsAlty", + "iterations": 100000, + "bits": 256 + }, + ... +} +``` #### Sharing @@ -407,12 +419,14 @@ previous request. It is sent as an unencrypted to-device event. Example: - { - "name": "org.example.some.secret", - "action": "request", - "requesting_device_id": "ABCDEFG", - "request_id": "randomly_generated_id_9573" - } +```json +{ + "name": "org.example.some.secret", + "action": "request", + "requesting_device_id": "ABCDEFG", + "request_id": "randomly_generated_id_9573" +} +``` ###### `m.secret.send` @@ -444,7 +458,9 @@ an `m.secret.request` event. It must be encrypted as an Example: - { - "request_id": "randomly_generated_id_9573", - "secret": "ThisIsASecretDon'tTellAnyone" - } +```json +{ + "request_id": "randomly_generated_id_9573", + "secret": "ThisIsASecretDon'tTellAnyone" +} +``` diff --git a/content/client-server-api/modules/send_to_device.md b/content/client-server-api/modules/send_to_device.md index 1090aa82..2df99bb5 100644 --- a/content/client-server-api/modules/send_to_device.md +++ b/content/client-server-api/modules/send_to_device.md @@ -125,19 +125,21 @@ This module adds the following properties to the \_ response: Example response: - { - "next_batch": "s72595_4483_1934", - "rooms": {"leave": {}, "join": {}, "invite": {}}, - "to_device": { - "events": [ - { - "sender": "@alice:example.com", - "type": "m.new_device", - "content": { - "device_id": "XYZABCDE", - "rooms": ["!726s6s6q:example.com"] - } - } - ] +```json +{ + "next_batch": "s72595_4483_1934", + "rooms": {"leave": {}, "join": {}, "invite": {}}, + "to_device": { + "events": [ + { + "sender": "@alice:example.com", + "type": "m.new_device", + "content": { + "device_id": "XYZABCDE", + "rooms": ["!726s6s6q:example.com"] + } } - } + ] + } +} +``` diff --git a/content/identity-service-api.md b/content/identity-service-api.md index 4521771e..4439a65e 100644 --- a/content/identity-service-api.md +++ b/content/identity-service-api.md @@ -69,10 +69,12 @@ communication, and all API calls use a Content-Type of Any errors which occur at the Matrix API level MUST return a "standard error response". This is a JSON object which looks like: - { - "errcode": "", - "error": "" - } +```json +{ + "errcode": "", + "error": "" +} +``` The `error` string will be a human-readable error message, usually a sentence explaining what went wrong. The `errcode` string will be a diff --git a/content/server-server-api.md b/content/server-server-api.md index e499d626..7c034aa9 100644 --- a/content/server-server-api.md +++ b/content/server-server-api.md @@ -248,18 +248,20 @@ and any query parameters if present, but should not include the leading Step 1 sign JSON: - { - "method": "GET", - "uri": "/target", - "origin": "origin.hs.example.com", - "destination": "destination.hs.example.com", - "content": , - "signatures": { - "origin.hs.example.com": { - "ed25519:key1": "ABCDEF..." - } +``` +{ + "method": "GET", + "uri": "/target", + "origin": "origin.hs.example.com", + "destination": "destination.hs.example.com", + "content": , + "signatures": { + "origin.hs.example.com": { + "ed25519:key1": "ABCDEF..." } } +} +``` The server names in the JSON above are the server names for each homeserver involved. Delegation from the [server name resolution @@ -277,31 +279,33 @@ Step 2 add Authorization header: Example python code: - def authorization_headers(origin_name, origin_signing_key, - destination_name, request_method, request_target, - content=None): - request_json = { - "method": request_method, - "uri": request_target, - "origin": origin_name, - "destination": destination_name, - } +```py +def authorization_headers(origin_name, origin_signing_key, + destination_name, request_method, request_target, + content=None): + request_json = { + "method": request_method, + "uri": request_target, + "origin": origin_name, + "destination": destination_name, + } - if content is not None: - request_json["content"] = content + if content is not None: + request_json["content"] = content - signed_json = sign_json(request_json, origin_name, origin_signing_key) + signed_json = sign_json(request_json, origin_name, origin_signing_key) - authorization_headers = [] + authorization_headers = [] - for key, sig in signed_json["signatures"][origin_name].items(): - authorization_headers.append(bytes( - "X-Matrix origin=%s,key=\"%s\",sig=\"%s\"" % ( - origin_name, key, sig, - ) - )) + for key, sig in signed_json["signatures"][origin_name].items(): + authorization_headers.append(bytes( + "X-Matrix origin=%s,key=\"%s\",sig=\"%s\"" % ( + origin_name, key, sig, + ) + )) - return ("Authorization", authorization_headers) + return ("Authorization", authorization_headers) +``` ### Response Authentication @@ -1121,49 +1125,51 @@ SHA-256. ### Example code - def hash_and_sign_event(event_object, signing_key, signing_name): - # First we need to hash the event object. - content_hash = compute_content_hash(event_object) - event_object["hashes"] = {"sha256": encode_unpadded_base64(content_hash)} - - # Strip all the keys that would be removed if the event was redacted. - # The hashes are not stripped and cover all the keys in the event. - # This means that we can tell if any of the non-essential keys are - # modified or removed. - stripped_object = strip_non_essential_keys(event_object) - - # Sign the stripped JSON object. The signature only covers the - # essential keys and the hashes. This means that we can check the - # signature even if the event is redacted. - signed_object = sign_json(stripped_object, signing_key, signing_name) - - # Copy the signatures from the stripped event to the original event. - event_object["signatures"] = signed_object["signatures"] - - def compute_content_hash(event_object): - # take a copy of the event before we remove any keys. - event_object = dict(event_object) - - # Keys under "unsigned" can be modified by other servers. - # They are useful for conveying information like the age of an - # event that will change in transit. - # Since they can be modified we need to exclude them from the hash. - event_object.pop("unsigned", None) - - # Signatures will depend on the current value of the "hashes" key. - # We cannot add new hashes without invalidating existing signatures. - event_object.pop("signatures", None) - - # The "hashes" key might contain multiple algorithms if we decide to - # migrate away from SHA-2. We don't want to include an existing hash - # output in our hash so we exclude the "hashes" dict from the hash. - event_object.pop("hashes", None) - - # Encode the JSON using a canonical encoding so that we get the same - # bytes on every server for the same JSON object. - event_json_bytes = encode_canonical_json(event_object) - - return hashlib.sha256(event_json_bytes) +```py +def hash_and_sign_event(event_object, signing_key, signing_name): + # First we need to hash the event object. + content_hash = compute_content_hash(event_object) + event_object["hashes"] = {"sha256": encode_unpadded_base64(content_hash)} + + # Strip all the keys that would be removed if the event was redacted. + # The hashes are not stripped and cover all the keys in the event. + # This means that we can tell if any of the non-essential keys are + # modified or removed. + stripped_object = strip_non_essential_keys(event_object) + + # Sign the stripped JSON object. The signature only covers the + # essential keys and the hashes. This means that we can check the + # signature even if the event is redacted. + signed_object = sign_json(stripped_object, signing_key, signing_name) + + # Copy the signatures from the stripped event to the original event. + event_object["signatures"] = signed_object["signatures"] + +def compute_content_hash(event_object): + # take a copy of the event before we remove any keys. + event_object = dict(event_object) + + # Keys under "unsigned" can be modified by other servers. + # They are useful for conveying information like the age of an + # event that will change in transit. + # Since they can be modified we need to exclude them from the hash. + event_object.pop("unsigned", None) + + # Signatures will depend on the current value of the "hashes" key. + # We cannot add new hashes without invalidating existing signatures. + event_object.pop("signatures", None) + + # The "hashes" key might contain multiple algorithms if we decide to + # migrate away from SHA-2. We don't want to include an existing hash + # output in our hash so we exclude the "hashes" dict from the hash. + event_object.pop("hashes", None) + + # Encode the JSON using a canonical encoding so that we get the same + # bytes on every server for the same JSON object. + event_json_bytes = encode_canonical_json(event_object) + + return hashlib.sha256(event_json_bytes) +``` ## Security considerations