Compare commits
227 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 43fee73924 | |||
| b72e9cb36c | |||
| 737dcc1d29 | |||
| dba08d230e | |||
| 15fb363874 | |||
| ceb4581f91 | |||
| 8217c0f05f | |||
| c5c27b3cb0 | |||
| 04bbfae08e | |||
| b3efa73eda | |||
| f3efac059c | |||
| 9fb4ed2ec0 | |||
| f19013143a | |||
| ea3ee9bea5 | |||
| ccca6f4b6d | |||
| 6a583d2ba6 | |||
| 4049a32871 | |||
| 331c9ce1ff | |||
| 81ab2aca37 | |||
| 564b8276bf | |||
| b4a93d2dc3 | |||
| 260040b919 | |||
| 8dbef8b68e | |||
| 458b2d422d | |||
| ee51357dbc | |||
| fa679e873d | |||
| ed3fded8e8 | |||
| 92df82bfa9 | |||
| 0dc9c27651 | |||
| f6f54c35a3 | |||
| 0a9959bffb | |||
| b3a16cb852 | |||
| 9beb259333 | |||
| 63c57e8e02 | |||
| 0448a7ea68 | |||
| 5bd005b28a | |||
| 3aec6367d1 | |||
| cea3831c20 | |||
| 18ccceca2d | |||
| fffcdcb514 | |||
| efadf374d6 | |||
| 55ecb40190 | |||
| 01f6b3dfc6 | |||
| 786590eadc | |||
| c9174188ba | |||
| 64fb79e0be | |||
| 088ff5d0aa | |||
| 99e58b0297 | |||
| f4d1c5c006 | |||
| f8a1e98de1 | |||
| 5487cf2070 | |||
| e998be3a9b | |||
| d70767ef3a | |||
| fbb355c5c9 | |||
| 20bc8071fc | |||
| 0438c6c51c | |||
| b39abba41e | |||
| e52e2f10bf | |||
| dfc19e79f1 | |||
| f59bd3da7a | |||
| 50791e3aa7 | |||
| 8211b2358f | |||
| f2e1f3393d | |||
| 0ffec0a32d | |||
| 1e5e705458 | |||
| f2af6ea60d | |||
| de9187fee2 | |||
| 5eed091185 | |||
| 06644b5748 | |||
| bb853f65e0 | |||
| eb830dd014 | |||
| de82d1e90c | |||
| 53e838083c | |||
| 975368de8f | |||
| 89173be055 | |||
| fe2bdd027e | |||
| b376a7c399 | |||
| 2df262d877 | |||
| 320ab050fe | |||
| 1816d7aa4c | |||
| 41b763f331 | |||
| 36db57615d | |||
| 8f7ed1dc15 | |||
| 83a8a0cf21 | |||
| ffb0e27efa | |||
| e71c4b3bc4 | |||
| 85a0adb004 | |||
| f1475cd3d7 | |||
| 8c14812537 | |||
| 27aedf0563 | |||
| 95c2c1643e | |||
| f952f6742f | |||
| f3a10a8166 | |||
| 0790201cca | |||
| 5938c49453 | |||
| 14fb080f80 | |||
| 034b8db070 | |||
| d3ce0cb82f | |||
| 4dbda8dffd | |||
| 01f32e0f45 | |||
| 9a0de545b8 | |||
| 86c530e967 | |||
| 049b769f68 | |||
| dcd6626fe6 | |||
| 601cefe975 | |||
| 1fc2ab7f7d | |||
| f2c5b2bd49 | |||
| f31f88ce31 | |||
| d35f5152a9 | |||
| d8e19db8bf | |||
| 376e56d5fd | |||
| 72f856eca4 | |||
| dbab75eae7 | |||
| 7457da80e9 | |||
| 443e01d38c | |||
| 880438c5c1 | |||
| 1984cf02cf | |||
| 5423d3ca61 | |||
| 3f448df1d3 | |||
| a626b44bbe | |||
| 4c6e2fca91 | |||
| ab4d9ae4bc | |||
| fb3d075da2 | |||
| 657e48de7e | |||
| 1b63cb1406 | |||
| 4bdabbfbe9 | |||
| 01f0dd4498 | |||
| f59650d8a6 | |||
| 0e444fd925 | |||
| 9b8b57d186 | |||
| ca6a52727c | |||
| 3dfde6bf6a | |||
| 780394b051 | |||
| 6942e3467b | |||
| 70eb8a7300 | |||
| 15a8c23cd0 | |||
| 49f0e368d0 | |||
| 590608a215 | |||
| 202fec2a35 | |||
| 817bfa35e5 | |||
| 110c9800f0 | |||
| 1a6dc973bb | |||
| 44dd674dab | |||
| 4a3ce640d7 | |||
| df6ebf83b4 | |||
| e5dcc5a407 | |||
| 1ee8abb0e6 | |||
| dd40435425 | |||
| 74cb57c761 | |||
| 86123f28f7 | |||
| f97ab32e7c | |||
| b0e2544e4b | |||
| 2a2a40af7a | |||
| 1df12d1677 | |||
| 14a2d7e860 | |||
| 3f2c05664f | |||
| 9b05d1d68e | |||
| 772d668389 | |||
| 03360a663e | |||
| e1e9f690c9 | |||
| 934e81d16c | |||
| 88bb31d3e6 | |||
| 33f5894547 | |||
| fa46d2bef8 | |||
| 65f8556ee9 | |||
| ebe174fbef | |||
| eaaeedbb37 | |||
| bf45c176a7 | |||
| 87a8e4c216 | |||
| 30cc7d4f0f | |||
| 4a47867e49 | |||
| 5fced642fa | |||
| 9fb559307b | |||
| 96c8c2b9c3 | |||
| 145cdf6985 | |||
| 5910fd95ff | |||
| c0dbf2df7f | |||
| cfaadef669 | |||
| eeffe208ec | |||
| 9dc61faa6f | |||
| 2173ab3437 | |||
| 66a6dd1f0c | |||
| 8a8109272a | |||
| 7ea30c449e | |||
| a6e4096773 | |||
| c1e2d646b6 | |||
| 710ac6847d | |||
| f0267eae36 | |||
| 1632ee3537 | |||
| a16cdb948c | |||
| c4ae27dae6 | |||
| 053bc49738 | |||
| 3a1de9fbdc | |||
| efcaadd0b4 | |||
| 0170cb066d | |||
| 6bba5ca25a | |||
| edcdeb31ea | |||
| 1286007b2e | |||
| 9faab093f7 | |||
| 64bf145e4b | |||
| 733008cfc4 | |||
| bab4582139 | |||
| fddf2843b4 | |||
| f8d83f8273 | |||
| cfeaf188ed | |||
| 58ad1ecbfe | |||
| 463538178d | |||
| 14907065d7 | |||
| ce2059a4b9 | |||
| 2bfc157e64 | |||
| fda7a2cf13 | |||
| e69de8c26f | |||
| f404c80714 | |||
| e2b680c223 | |||
| bdaf2e3b4f | |||
| 2190022e64 | |||
| e000e2b9fd | |||
| d5000820fd | |||
| 9d91d197e4 | |||
| 5b767ae948 | |||
| 49448fafaa | |||
| 4116d89d5f | |||
| cc192efe45 | |||
| feef1a35b9 | |||
| 55a2f46604 | |||
| ed8b303400 | |||
| 4acd06eaba |
@@ -1,15 +1,19 @@
|
||||
{
|
||||
"presets": ["es2015", "es2016"],
|
||||
"plugins": [
|
||||
"transform-class-properties",
|
||||
|
||||
// this transforms async functions into generator functions, which
|
||||
// are then made to use the regenerator module by babel's
|
||||
// transform-regnerator plugin (which is enabled by es2015).
|
||||
"transform-async-to-bluebird",
|
||||
|
||||
// This makes sure that the regenerator runtime is available to
|
||||
// the transpiled code.
|
||||
"transform-runtime",
|
||||
"sourceMaps": true,
|
||||
"presets": [
|
||||
["@babel/preset-env", {
|
||||
"targets": {
|
||||
"node": 10
|
||||
},
|
||||
"modules": "commonjs"
|
||||
}],
|
||||
"@babel/preset-typescript"
|
||||
],
|
||||
"plugins": [
|
||||
"@babel/plugin-proposal-numeric-separator",
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
"@babel/plugin-proposal-object-rest-spread",
|
||||
"@babel/plugin-syntax-dynamic-import",
|
||||
"@babel/plugin-transform-runtime"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,11 +1,35 @@
|
||||
steps:
|
||||
- label: ":eslint: Lint"
|
||||
- label: ":eslint: JS Lint"
|
||||
command:
|
||||
- "yarn install"
|
||||
- "yarn lint"
|
||||
- "yarn lint:js"
|
||||
plugins:
|
||||
- docker#v3.0.1:
|
||||
image: "node:10"
|
||||
image: "node:12"
|
||||
|
||||
- label: ":tslint: TS Lint"
|
||||
command:
|
||||
- "yarn install"
|
||||
- "yarn lint:ts"
|
||||
plugins:
|
||||
- docker#v3.0.1:
|
||||
image: "node:12"
|
||||
|
||||
- label: ":typescript: Types Lint"
|
||||
command:
|
||||
- "yarn install"
|
||||
- "yarn lint:types"
|
||||
plugins:
|
||||
- docker#v3.0.1:
|
||||
image: "node:12"
|
||||
|
||||
- label: "🛠 Build"
|
||||
command:
|
||||
- "yarn install"
|
||||
- "yarn build"
|
||||
plugins:
|
||||
- docker#v3.0.1:
|
||||
image: "node:12"
|
||||
|
||||
- label: ":jest: Tests"
|
||||
command:
|
||||
@@ -13,7 +37,7 @@ steps:
|
||||
- "yarn test"
|
||||
plugins:
|
||||
- docker#v3.0.1:
|
||||
image: "node:10"
|
||||
image: "node:12"
|
||||
|
||||
- label: "📃 Docs"
|
||||
command:
|
||||
@@ -21,7 +45,7 @@ steps:
|
||||
- "yarn gendoc"
|
||||
plugins:
|
||||
- docker#v3.0.1:
|
||||
image: "node:10"
|
||||
image: "node:12"
|
||||
|
||||
- wait
|
||||
|
||||
|
||||
+118
@@ -1,3 +1,121 @@
|
||||
Changes in [4.0.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v4.0.0-rc.1) (2020-01-20)
|
||||
==========================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v3.0.0...v4.0.0-rc.1)
|
||||
|
||||
BREAKING CHANGES
|
||||
================
|
||||
* The js-sdk node module now exports ES6 rather than ES5. If you
|
||||
wish to supports target that aren't compatible with ES6, you
|
||||
will need to transpile the js-sdk to a suitable dialect.
|
||||
|
||||
All Changes
|
||||
===========
|
||||
* Convert secret storage to new account data API
|
||||
[\#1154](https://github.com/matrix-org/matrix-js-sdk/pull/1154)
|
||||
* Add v5 as a safe room version
|
||||
[\#1157](https://github.com/matrix-org/matrix-js-sdk/pull/1157)
|
||||
* Add API to get account data from server
|
||||
[\#1153](https://github.com/matrix-org/matrix-js-sdk/pull/1153)
|
||||
* Fix sourcemaps by refactoring the build system
|
||||
[\#1151](https://github.com/matrix-org/matrix-js-sdk/pull/1151)
|
||||
* record, report, and notify about olm errors
|
||||
[\#1146](https://github.com/matrix-org/matrix-js-sdk/pull/1146)
|
||||
* Send device messages for the same user in same API call.
|
||||
[\#1148](https://github.com/matrix-org/matrix-js-sdk/pull/1148)
|
||||
* Add an option to ignore unverified devices
|
||||
[\#1150](https://github.com/matrix-org/matrix-js-sdk/pull/1150)
|
||||
* Sign key backup with cross-signing key on upgrade
|
||||
[\#1144](https://github.com/matrix-org/matrix-js-sdk/pull/1144)
|
||||
* Emoji verification: Change name of 🔒 to lock
|
||||
[\#1145](https://github.com/matrix-org/matrix-js-sdk/pull/1145)
|
||||
* use a separate object for each encrypted content
|
||||
[\#1147](https://github.com/matrix-org/matrix-js-sdk/pull/1147)
|
||||
* Sourcemaps: develop -> feature branch
|
||||
[\#1143](https://github.com/matrix-org/matrix-js-sdk/pull/1143)
|
||||
* Use a safer import/export scheme for the ContentRepo utilities
|
||||
[\#1134](https://github.com/matrix-org/matrix-js-sdk/pull/1134)
|
||||
* Fix error handling in decryptGroupMessage
|
||||
[\#1142](https://github.com/matrix-org/matrix-js-sdk/pull/1142)
|
||||
* Add additional properties to package.json for riot-web's webpack
|
||||
[\#1131](https://github.com/matrix-org/matrix-js-sdk/pull/1131)
|
||||
* Fix import for indexeddb crypto store
|
||||
[\#1133](https://github.com/matrix-org/matrix-js-sdk/pull/1133)
|
||||
* Use the right request when creating clients
|
||||
[\#1132](https://github.com/matrix-org/matrix-js-sdk/pull/1132)
|
||||
* Target NodeJS 10, minified browser bundle, and other publishing/package
|
||||
things
|
||||
[\#1127](https://github.com/matrix-org/matrix-js-sdk/pull/1127)
|
||||
* Re-focus sourcemap generation
|
||||
[\#1126](https://github.com/matrix-org/matrix-js-sdk/pull/1126)
|
||||
* Remove ancient polyfill for prototype inheritance
|
||||
[\#1125](https://github.com/matrix-org/matrix-js-sdk/pull/1125)
|
||||
* Remove "source-map-support" from tests because it makes sourcemaps worse
|
||||
[\#1124](https://github.com/matrix-org/matrix-js-sdk/pull/1124)
|
||||
* Remove ancient "use strict" annotations
|
||||
[\#1123](https://github.com/matrix-org/matrix-js-sdk/pull/1123)
|
||||
* Use ES6 imports/exports instead of older CommonJS ones
|
||||
[\#1122](https://github.com/matrix-org/matrix-js-sdk/pull/1122)
|
||||
* [BREAKING] Refactor the entire build process
|
||||
[\#1113](https://github.com/matrix-org/matrix-js-sdk/pull/1113)
|
||||
|
||||
Changes in [3.0.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v3.0.0) (2020-01-13)
|
||||
================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v3.0.0-rc.1...v3.0.0)
|
||||
|
||||
* No changes from rc.1
|
||||
|
||||
Changes in [3.0.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v3.0.0-rc.1) (2020-01-06)
|
||||
==========================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.4.6...v3.0.0-rc.1)
|
||||
|
||||
BREAKING CHANGES
|
||||
================
|
||||
* matrix-js-sdk no longer uses bluebird promises, so promises returned
|
||||
by the js-sdk no longer support the done() method. Code that calls
|
||||
done() on promises returned by the js-sdk will break and will need
|
||||
to be updated to remove the done() call.
|
||||
|
||||
All Changes
|
||||
===========
|
||||
* Make displayName disambiguation more fuzzy especially against RTL/LTR
|
||||
content
|
||||
[\#1141](https://github.com/matrix-org/matrix-js-sdk/pull/1141)
|
||||
* stop trying to resend event if we get M_TOO_LARGE
|
||||
[\#1129](https://github.com/matrix-org/matrix-js-sdk/pull/1129)
|
||||
* Fix creating a key backup with cross signing diabled
|
||||
[\#1139](https://github.com/matrix-org/matrix-js-sdk/pull/1139)
|
||||
* Use checkDeviceTrust with key backup
|
||||
[\#1138](https://github.com/matrix-org/matrix-js-sdk/pull/1138)
|
||||
* Add support for passthrough SSSS secrets
|
||||
[\#1128](https://github.com/matrix-org/matrix-js-sdk/pull/1128)
|
||||
* Add support for key backups using secret storage
|
||||
[\#1118](https://github.com/matrix-org/matrix-js-sdk/pull/1118)
|
||||
* Remove unused user verification event
|
||||
[\#1117](https://github.com/matrix-org/matrix-js-sdk/pull/1117)
|
||||
* Fix check for private keys
|
||||
[\#1116](https://github.com/matrix-org/matrix-js-sdk/pull/1116)
|
||||
* Restore watching mode for `start:watch`
|
||||
[\#1115](https://github.com/matrix-org/matrix-js-sdk/pull/1115)
|
||||
* Add secret storage bootstrap flow
|
||||
[\#1079](https://github.com/matrix-org/matrix-js-sdk/pull/1079)
|
||||
* Part 1 of many: Upgrade to babel@7 and TypeScript
|
||||
[\#1112](https://github.com/matrix-org/matrix-js-sdk/pull/1112)
|
||||
* Remove Bluebird: phase 2.5
|
||||
[\#1100](https://github.com/matrix-org/matrix-js-sdk/pull/1100)
|
||||
* Remove Bluebird: phase 3
|
||||
[\#1088](https://github.com/matrix-org/matrix-js-sdk/pull/1088)
|
||||
* ignore m.key.verification.done messages when we don't expect any more
|
||||
messages
|
||||
[\#1104](https://github.com/matrix-org/matrix-js-sdk/pull/1104)
|
||||
* dont cancel on remote echo of own .request event
|
||||
[\#1111](https://github.com/matrix-org/matrix-js-sdk/pull/1111)
|
||||
* Refactor verification request code
|
||||
[\#1109](https://github.com/matrix-org/matrix-js-sdk/pull/1109)
|
||||
* Fix device list's cross-signing storage path
|
||||
[\#1105](https://github.com/matrix-org/matrix-js-sdk/pull/1105)
|
||||
* yarn upgrade
|
||||
[\#1103](https://github.com/matrix-org/matrix-js-sdk/pull/1103)
|
||||
|
||||
Changes in [2.4.6](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v2.4.6) (2019-12-09)
|
||||
================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.4.6-rc.1...v2.4.6)
|
||||
|
||||
+10
-2
@@ -36,8 +36,16 @@ minutes.
|
||||
Code style
|
||||
~~~~~~~~~~
|
||||
|
||||
The code-style for matrix-js-sdk is not formally documented, but contributors
|
||||
are encouraged to read the code style document for matrix-react-sdk
|
||||
The js-sdk aims to target TypeScript/ES6. All new files should be written in
|
||||
TypeScript and existing files should use ES6 principles where possible.
|
||||
|
||||
Members should not be exported as a default export in general - it causes problems
|
||||
with the architecture of the SDK (index file becomes less clear) and could
|
||||
introduce naming problems (as default exports get aliased upon import). In
|
||||
general, avoid using `export default`.
|
||||
|
||||
The remaining code-style for matrix-js-sdk is not formally documented, but
|
||||
contributors are encouraged to read the code style document for matrix-react-sdk
|
||||
(`<https://github.com/matrix-org/matrix-react-sdk/blob/master/code_style.md>`_)
|
||||
and follow the principles set out there.
|
||||
|
||||
|
||||
@@ -9,12 +9,16 @@ Quickstart
|
||||
|
||||
In a browser
|
||||
------------
|
||||
Download either the full or minified version from
|
||||
Download the browser version from
|
||||
https://github.com/matrix-org/matrix-js-sdk/releases/latest and add that as a
|
||||
``<script>`` to your page. There will be a global variable ``matrixcs``
|
||||
attached to ``window`` through which you can access the SDK. See below for how to
|
||||
include libolm to enable end-to-end-encryption.
|
||||
|
||||
The browser bundle supports recent versions of browsers. Typically this is ES2015
|
||||
or `> 0.5%, last 2 versions, Firefox ESR, not dead` if using
|
||||
[browserlists](https://github.com/browserslist/browserslist).
|
||||
|
||||
Please check [the working browser example](examples/browser) for more information.
|
||||
|
||||
In Node.js
|
||||
@@ -22,13 +26,18 @@ In Node.js
|
||||
|
||||
Ensure you have the latest LTS version of Node.js installed.
|
||||
|
||||
Using `yarn` instead of `npm` is recommended. Please see the Yarn [install guide](https://yarnpkg.com/docs/install/) if you do not have it already.
|
||||
This SDK targets Node 10 for compatibility, which translates to ES6. If you're using
|
||||
a bundler like webpack you'll likely have to transpile dependencies, including this
|
||||
SDK, to match your target browsers.
|
||||
|
||||
Using `yarn` instead of `npm` is recommended. Please see the Yarn [install guide](https://yarnpkg.com/docs/install/)
|
||||
if you do not have it already.
|
||||
|
||||
``yarn add matrix-js-sdk``
|
||||
|
||||
```javascript
|
||||
var sdk = require("matrix-js-sdk");
|
||||
var client = sdk.createClient("https://matrix.org");
|
||||
import * as sdk from "matrix-js-sdk";
|
||||
const client = sdk.createClient("https://matrix.org");
|
||||
client.publicRooms(function(err, data) {
|
||||
console.log("Public Rooms: %s", JSON.stringify(data));
|
||||
});
|
||||
@@ -59,7 +68,7 @@ client.once('sync', function(state, prevState, res) {
|
||||
To send a message:
|
||||
|
||||
```javascript
|
||||
var content = {
|
||||
const content = {
|
||||
"body": "message text",
|
||||
"msgtype": "m.text"
|
||||
};
|
||||
@@ -161,7 +170,7 @@ which will be fulfilled in the future.
|
||||
The typical usage is something like:
|
||||
|
||||
```javascript
|
||||
matrixClient.someMethod(arg1, arg2).done(function(result) {
|
||||
matrixClient.someMethod(arg1, arg2).then(function(result) {
|
||||
...
|
||||
});
|
||||
```
|
||||
@@ -191,10 +200,10 @@ This section provides some useful code snippets which demonstrate the
|
||||
core functionality of the SDK. These examples assume the SDK is setup like this:
|
||||
|
||||
```javascript
|
||||
var sdk = require("matrix-js-sdk");
|
||||
var myUserId = "@example:localhost";
|
||||
var myAccessToken = "QGV4YW1wbGU6bG9jYWxob3N0.qPEvLuYfNBjxikiCjP";
|
||||
var matrixClient = sdk.createClient({
|
||||
import * as sdk from "matrix-js-sdk";
|
||||
const myUserId = "@example:localhost";
|
||||
const myAccessToken = "QGV4YW1wbGU6bG9jYWxob3N0.qPEvLuYfNBjxikiCjP";
|
||||
const matrixClient = sdk.createClient({
|
||||
baseUrl: "http://localhost:8008",
|
||||
accessToken: myAccessToken,
|
||||
userId: myUserId
|
||||
@@ -206,7 +215,7 @@ core functionality of the SDK. These examples assume the SDK is setup like this:
|
||||
```javascript
|
||||
matrixClient.on("RoomMember.membership", function(event, member) {
|
||||
if (member.membership === "invite" && member.userId === myUserId) {
|
||||
matrixClient.joinRoom(member.roomId).done(function() {
|
||||
matrixClient.joinRoom(member.roomId).then(function() {
|
||||
console.log("Auto-joined %s", member.roomId);
|
||||
});
|
||||
}
|
||||
@@ -247,11 +256,11 @@ Output:
|
||||
|
||||
```javascript
|
||||
matrixClient.on("RoomState.members", function(event, state, member) {
|
||||
var room = matrixClient.getRoom(state.roomId);
|
||||
const room = matrixClient.getRoom(state.roomId);
|
||||
if (!room) {
|
||||
return;
|
||||
}
|
||||
var memberList = state.getMembers();
|
||||
const memberList = state.getMembers();
|
||||
console.log(room.name);
|
||||
console.log(Array(room.name.length + 1).join("=")); // underline
|
||||
for (var i = 0; i < memberList.length; i++) {
|
||||
@@ -297,7 +306,7 @@ End-to-end encryption support
|
||||
=============================
|
||||
|
||||
The SDK supports end-to-end encryption via the Olm and Megolm protocols, using
|
||||
[libolm](https://gitlab.matrix.org/matrix-org/olm). It is left up to the
|
||||
[libolm](https://gitlab.matrix.org/matrix-org/olm). It is left up to the
|
||||
application to make libolm available, via the ``Olm`` global.
|
||||
|
||||
It is also necessry to call ``matrixClient.initCrypto()`` after creating a new
|
||||
@@ -319,7 +328,7 @@ To provide the Olm library in a browser application:
|
||||
|
||||
* download the transpiled libolm (from https://packages.matrix.org/npm/olm/).
|
||||
* load ``olm.js`` as a ``<script>`` *before* ``browser-matrix.js``.
|
||||
|
||||
|
||||
To provide the Olm library in a node.js application:
|
||||
|
||||
* ``yarn add https://packages.matrix.org/npm/olm/olm-3.1.4.tgz``
|
||||
@@ -351,11 +360,6 @@ To build a browser version from scratch when developing::
|
||||
$ yarn build
|
||||
```
|
||||
|
||||
To constantly do builds when files are modified (using ``watchify``)::
|
||||
```
|
||||
$ yarn watch
|
||||
```
|
||||
|
||||
To run tests (Jasmine)::
|
||||
```
|
||||
$ yarn test
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
var matrixcs = require("./lib/matrix");
|
||||
const request = require('browser-request');
|
||||
const queryString = require('qs');
|
||||
|
||||
matrixcs.request(function(opts, fn) {
|
||||
// We manually fix the query string for browser-request because
|
||||
// it doesn't correctly handle cases like ?via=one&via=two. Instead
|
||||
// we mimic `request`'s query string interface to make it all work
|
||||
// as expected.
|
||||
// browser-request will happily take the constructed string as the
|
||||
// query string without trying to modify it further.
|
||||
opts.qs = queryString.stringify(opts.qs || {}, opts.qsStringifyOptions);
|
||||
return request(opts, fn);
|
||||
});
|
||||
|
||||
// just *accessing* indexedDB throws an exception in firefox with
|
||||
// indexeddb disabled.
|
||||
var indexedDB;
|
||||
try {
|
||||
indexedDB = global.indexedDB;
|
||||
} catch(e) {}
|
||||
|
||||
// if our browser (appears to) support indexeddb, use an indexeddb crypto store.
|
||||
if (indexedDB) {
|
||||
matrixcs.setCryptoStoreFactory(
|
||||
function() {
|
||||
return new matrixcs.IndexedDBCryptoStore(
|
||||
indexedDB, "matrix-js-sdk:crypto"
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = matrixcs; // keep export for browserify package deps
|
||||
global.matrixcs = matrixcs;
|
||||
@@ -1,4 +1,3 @@
|
||||
"use strict";
|
||||
console.log("Loading browser sdk");
|
||||
|
||||
var client = matrixcs.createClient("http://matrix.org");
|
||||
|
||||
+9
-11
@@ -1,5 +1,3 @@
|
||||
"use strict";
|
||||
|
||||
var myUserId = "@example:localhost";
|
||||
var myAccessToken = "QGV4YW1wbGU6bG9jYWxob3N0.qPEvLuYfNBjxikiCjP";
|
||||
var sdk = require("matrix-js-sdk");
|
||||
@@ -56,7 +54,7 @@ rl.on('line', function(line) {
|
||||
}
|
||||
}
|
||||
if (notSentEvent) {
|
||||
matrixClient.resendEvent(notSentEvent, viewingRoom).done(function() {
|
||||
matrixClient.resendEvent(notSentEvent, viewingRoom).then(function() {
|
||||
printMessages();
|
||||
rl.prompt();
|
||||
}, function(err) {
|
||||
@@ -70,7 +68,7 @@ rl.on('line', function(line) {
|
||||
}
|
||||
else if (line.indexOf("/more ") === 0) {
|
||||
var amount = parseInt(line.split(" ")[1]) || 20;
|
||||
matrixClient.scrollback(viewingRoom, amount).done(function(room) {
|
||||
matrixClient.scrollback(viewingRoom, amount).then(function(room) {
|
||||
printMessages();
|
||||
rl.prompt();
|
||||
}, function(err) {
|
||||
@@ -79,7 +77,7 @@ rl.on('line', function(line) {
|
||||
}
|
||||
else if (line.indexOf("/invite ") === 0) {
|
||||
var userId = line.split(" ")[1].trim();
|
||||
matrixClient.invite(viewingRoom.roomId, userId).done(function() {
|
||||
matrixClient.invite(viewingRoom.roomId, userId).then(function() {
|
||||
printMessages();
|
||||
rl.prompt();
|
||||
}, function(err) {
|
||||
@@ -92,7 +90,7 @@ rl.on('line', function(line) {
|
||||
matrixClient.uploadContent({
|
||||
stream: stream,
|
||||
name: filename
|
||||
}).done(function(url) {
|
||||
}).then(function(url) {
|
||||
var content = {
|
||||
msgtype: "m.file",
|
||||
body: filename,
|
||||
@@ -116,7 +114,7 @@ rl.on('line', function(line) {
|
||||
viewingRoom = roomList[roomIndex];
|
||||
if (viewingRoom.getMember(myUserId).membership === "invite") {
|
||||
// join the room first
|
||||
matrixClient.joinRoom(viewingRoom.roomId).done(function(room) {
|
||||
matrixClient.joinRoom(viewingRoom.roomId).then(function(room) {
|
||||
setRoomList();
|
||||
viewingRoom = room;
|
||||
printMessages();
|
||||
@@ -128,7 +126,7 @@ rl.on('line', function(line) {
|
||||
else {
|
||||
printMessages();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
rl.prompt();
|
||||
});
|
||||
@@ -281,8 +279,8 @@ function printMemberList(room) {
|
||||
member.membership + new Array(10 - member.membership.length).join(" ")
|
||||
);
|
||||
print(
|
||||
"%s"+fmt(" :: ")+"%s"+fmt(" (")+"%s"+fmt(")"),
|
||||
membershipWithPadding, member.name,
|
||||
"%s"+fmt(" :: ")+"%s"+fmt(" (")+"%s"+fmt(")"),
|
||||
membershipWithPadding, member.name,
|
||||
(member.userId === myUserId ? "Me" : member.userId),
|
||||
fmt
|
||||
);
|
||||
@@ -295,7 +293,7 @@ function printRoomInfo(room) {
|
||||
var sendHeader = " Sender ";
|
||||
// pad content to 100
|
||||
var restCount = (
|
||||
100 - "Content".length - " | ".length - " | ".length -
|
||||
100 - "Content".length - " | ".length - " | ".length -
|
||||
eTypeHeader.length - sendHeader.length
|
||||
);
|
||||
var padSide = new Array(Math.floor(restCount/2)).join(" ");
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
"use strict";
|
||||
console.log("Loading browser sdk");
|
||||
var BASE_URL = "https://matrix.org";
|
||||
var TOKEN = "accesstokengoeshere";
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
var matrixcs = require("./lib/matrix");
|
||||
matrixcs.request(require("request"));
|
||||
module.exports = matrixcs;
|
||||
|
||||
var utils = require("./lib/utils");
|
||||
utils.runPolyfills();
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"tags": {
|
||||
"allowUnknownTags": true
|
||||
},
|
||||
"plugins": [
|
||||
"node_modules/better-docs/category",
|
||||
"node_modules/better-docs/typescript"
|
||||
],
|
||||
"source": {
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"includePattern": ".(ts|js)$"
|
||||
},
|
||||
"opts": {
|
||||
"encoding": "utf8",
|
||||
"destination": ".jsdoc",
|
||||
"readme": "README.md",
|
||||
"recurse": true,
|
||||
"verbose": true,
|
||||
"template": "node_modules/better-docs"
|
||||
}
|
||||
}
|
||||
+48
-48
@@ -1,21 +1,23 @@
|
||||
{
|
||||
"name": "matrix-js-sdk",
|
||||
"version": "2.4.6",
|
||||
"version": "4.0.0-rc.1",
|
||||
"description": "Matrix Client-Server SDK for Javascript",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test:watch": "jest spec/ --coverage --testEnvironment node --watch",
|
||||
"test": "jest spec/ --coverage --testEnvironment node",
|
||||
"gendoc": "babel --no-babelrc --plugins transform-class-properties -d .jsdocbuild src && jsdoc -r .jsdocbuild -P package.json -R README.md -d .jsdoc",
|
||||
"start": "yarn start:init && yarn start:watch",
|
||||
"start:watch": "babel -s -w --skip-initial-build -d lib src",
|
||||
"start:init": "babel -s -d lib src",
|
||||
"start": "echo THIS IS FOR LEGACY PURPOSES ONLY. && babel src -w -s -d lib --verbose --extensions \".ts,.js\"",
|
||||
"dist": "echo 'This is for the release script so it can make assets (browser bundle).' && yarn build",
|
||||
"clean": "rimraf lib dist",
|
||||
"build": "babel -s -d lib src && rimraf dist && mkdir dist && browserify -d browser-index.js | exorcist dist/browser-matrix.js.map > dist/browser-matrix.js && terser -c -m -o dist/browser-matrix.min.js --source-map \"content='dist/browser-matrix.js.map'\" dist/browser-matrix.js",
|
||||
"dist": "yarn build",
|
||||
"watch": "watchify -d browser-index.js -o 'exorcist dist/browser-matrix.js.map > dist/browser-matrix.js' -v",
|
||||
"lint": "eslint --max-warnings 93 src spec",
|
||||
"prepare": "yarn clean && yarn build && git rev-parse HEAD > git-revision.txt"
|
||||
"build": "yarn clean && git rev-parse HEAD > git-revision.txt && yarn build:compile && yarn build:compile-browser && yarn build:minify-browser && yarn build:types",
|
||||
"build:types": "tsc --emitDeclarationOnly",
|
||||
"build:compile": "babel -d lib --verbose --extensions \".ts,.js\" src",
|
||||
"build:compile-browser": "mkdirp dist && browserify -d src/browser-index.js -p [ tsify -p ./tsconfig.json ] -t [ babelify --sourceMaps=inline --presets [ @babel/preset-env @babel/preset-typescript ] ] | exorcist dist/browser-matrix.js.map > dist/browser-matrix.js",
|
||||
"build:minify-browser": "terser dist/browser-matrix.js --compress --mangle --source-map --output dist/browser-matrix.min.js",
|
||||
"gendoc": "jsdoc -c jsdoc.json -P package.json",
|
||||
"lint": "yarn lint:types && yarn lint:ts && yarn lint:js",
|
||||
"lint:js": "eslint --max-warnings 93 src spec",
|
||||
"lint:types": "tsc --noEmit",
|
||||
"lint:ts": "tslint --project ./tsconfig.json -t stylish",
|
||||
"test": "jest spec/ --coverage --testEnvironment node",
|
||||
"test:watch": "jest spec/ --coverage --testEnvironment node --watch"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -24,33 +26,26 @@
|
||||
"keywords": [
|
||||
"matrix-org"
|
||||
],
|
||||
"browser": "browser-index.js",
|
||||
"main": "./lib/index.js",
|
||||
"typings": "./lib/index.d.ts",
|
||||
"browser": "./lib/browser-index.js",
|
||||
"matrix_src_main": "./src/index.ts",
|
||||
"matrix_src_browser": "./src/browser-index.js",
|
||||
"author": "matrix.org",
|
||||
"license": "Apache-2.0",
|
||||
"files": [
|
||||
".babelrc",
|
||||
".eslintrc.js",
|
||||
"spec/.eslintrc.js",
|
||||
"lib",
|
||||
"src",
|
||||
"git-revision.txt",
|
||||
"CHANGELOG.md",
|
||||
"CONTRIBUTING.rst",
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"RELEASING.md",
|
||||
"examples",
|
||||
"git-hooks",
|
||||
"git-revision.txt",
|
||||
"index.js",
|
||||
"browser-index.js",
|
||||
"lib",
|
||||
"package.json",
|
||||
"release.sh",
|
||||
"spec",
|
||||
"src"
|
||||
"release.sh"
|
||||
],
|
||||
"dependencies": {
|
||||
"another-json": "^0.2.0",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"bluebird": "3.5.5",
|
||||
"browser-request": "^0.3.3",
|
||||
"bs58": "^4.0.1",
|
||||
"content-type": "^1.0.2",
|
||||
@@ -60,34 +55,39 @@
|
||||
"unhomoglyph": "^1.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.18.0",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"babel-jest": "^23.6.0",
|
||||
"babel-plugin-transform-async-to-bluebird": "^1.1.1",
|
||||
"babel-plugin-transform-class-properties": "^6.24.1",
|
||||
"babel-plugin-transform-runtime": "^6.23.0",
|
||||
"babel-preset-es2015": "^6.18.0",
|
||||
"babel-preset-es2016": "^6.24.1",
|
||||
"browserify": "^16.2.3",
|
||||
"browserify-shim": "^3.8.13",
|
||||
"@babel/cli": "^7.7.5",
|
||||
"@babel/core": "^7.7.5",
|
||||
"@babel/plugin-proposal-class-properties": "^7.7.4",
|
||||
"@babel/plugin-proposal-numeric-separator": "^7.7.4",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.7.4",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.7.4",
|
||||
"@babel/plugin-transform-runtime": "^7.7.6",
|
||||
"@babel/preset-env": "^7.7.6",
|
||||
"@babel/preset-typescript": "^7.7.4",
|
||||
"@babel/register": "^7.7.4",
|
||||
"@babel/runtime": "^7.7.6",
|
||||
"@types/node": "12",
|
||||
"babel-eslint": "^10.0.3",
|
||||
"babel-jest": "^24.9.0",
|
||||
"babelify": "^10.0.0",
|
||||
"better-docs": "^1.4.7",
|
||||
"browserify": "^16.5.0",
|
||||
"eslint": "^5.12.0",
|
||||
"eslint-config-google": "^0.7.1",
|
||||
"eslint-plugin-babel": "^5.3.0",
|
||||
"eslint-plugin-jest": "^23.0.4",
|
||||
"exorcist": "^1.0.1",
|
||||
"jest": "^23.6.0",
|
||||
"jest": "^24.9.0",
|
||||
"jsdoc": "^3.5.5",
|
||||
"matrix-mock-request": "^1.2.3",
|
||||
"olm": "https://packages.matrix.org/npm/olm/olm-3.1.4.tgz",
|
||||
"rimraf": "^3.0.0",
|
||||
"source-map-support": "^0.5.13",
|
||||
"sourceify": "^1.0.0",
|
||||
"terser": "^4.3.8",
|
||||
"watchify": "^3.11.1"
|
||||
"terser": "^4.4.3",
|
||||
"tsify": "^4.0.1",
|
||||
"tslint": "^5.20.1",
|
||||
"typescript": "^3.7.3"
|
||||
},
|
||||
"browserify": {
|
||||
"transform": [
|
||||
"sourceify"
|
||||
]
|
||||
"jest": {
|
||||
"testEnvironment": "node"
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Script to perform a release of matrix-js-sdk.
|
||||
# Script to perform a release of matrix-js-sdk and downstream projects.
|
||||
#
|
||||
# Requires:
|
||||
# github-changelog-generator; install via:
|
||||
@@ -9,6 +9,8 @@
|
||||
# hub; install via brew (macOS) or source/pre-compiled binaries (debian) (https://github.com/github/hub) - Tested on v2.2.9
|
||||
# npm; typically installed by Node.js
|
||||
# yarn; install via brew (macOS) or similar (https://yarnpkg.com/docs/install/)
|
||||
#
|
||||
# Note: this script is also used to release matrix-react-sdk and riot-web.
|
||||
|
||||
set -e
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2019 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.
|
||||
@@ -18,7 +19,7 @@ limitations under the License.
|
||||
* A mock implementation of the webstorage api
|
||||
* @constructor
|
||||
*/
|
||||
function MockStorageApi() {
|
||||
export function MockStorageApi() {
|
||||
this.data = {};
|
||||
this.keys = [];
|
||||
this.length = 0;
|
||||
@@ -52,5 +53,3 @@ MockStorageApi.prototype = {
|
||||
},
|
||||
};
|
||||
|
||||
/** */
|
||||
module.exports = MockStorageApi;
|
||||
|
||||
+12
-13
@@ -16,17 +16,16 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
// load olm before the sdk if possible
|
||||
import './olm-loader';
|
||||
|
||||
import sdk from '..';
|
||||
import testUtils from './test-utils';
|
||||
import MockHttpBackend from 'matrix-mock-request';
|
||||
import Promise from 'bluebird';
|
||||
import LocalStorageCryptoStore from '../lib/crypto/store/localStorage-crypto-store';
|
||||
import logger from '../src/logger';
|
||||
import {LocalStorageCryptoStore} from '../src/crypto/store/localStorage-crypto-store';
|
||||
import {logger} from '../src/logger';
|
||||
import {WebStorageSessionStore} from "../src/store/session/webstorage";
|
||||
import {syncPromise} from "./test-utils";
|
||||
import {createClient} from "../src/matrix";
|
||||
import {MockStorageApi} from "./MockStorageApi";
|
||||
|
||||
/**
|
||||
* Wrapper for a MockStorageApi, MockHttpBackend and MatrixClient
|
||||
@@ -40,16 +39,16 @@ import logger from '../src/logger';
|
||||
* session store. If undefined, we will create a MockStorageApi.
|
||||
* @param {object} options additional options to pass to the client
|
||||
*/
|
||||
export default function TestClient(
|
||||
export function TestClient(
|
||||
userId, deviceId, accessToken, sessionStoreBackend, options,
|
||||
) {
|
||||
this.userId = userId;
|
||||
this.deviceId = deviceId;
|
||||
|
||||
if (sessionStoreBackend === undefined) {
|
||||
sessionStoreBackend = new testUtils.MockStorageApi();
|
||||
sessionStoreBackend = new MockStorageApi();
|
||||
}
|
||||
const sessionStore = new sdk.WebStorageSessionStore(sessionStoreBackend);
|
||||
const sessionStore = new WebStorageSessionStore(sessionStoreBackend);
|
||||
|
||||
this.httpBackend = new MockHttpBackend();
|
||||
|
||||
@@ -66,7 +65,7 @@ export default function TestClient(
|
||||
this.cryptoStore = new LocalStorageCryptoStore(sessionStoreBackend);
|
||||
options.cryptoStore = this.cryptoStore;
|
||||
}
|
||||
this.client = sdk.createClient(options);
|
||||
this.client = createClient(options);
|
||||
|
||||
this.deviceKeys = null;
|
||||
this.oneTimeKeys = {};
|
||||
@@ -98,7 +97,7 @@ TestClient.prototype.start = function() {
|
||||
|
||||
return Promise.all([
|
||||
this.httpBackend.flushAllExpected(),
|
||||
testUtils.syncPromise(this.client),
|
||||
syncPromise(this.client),
|
||||
]).then(() => {
|
||||
logger.log(this + ': started');
|
||||
});
|
||||
@@ -226,7 +225,7 @@ TestClient.prototype.flushSync = function() {
|
||||
logger.log(`${this}: flushSync`);
|
||||
return Promise.all([
|
||||
this.httpBackend.flush('/sync', 1),
|
||||
testUtils.syncPromise(this.client),
|
||||
syncPromise(this.client),
|
||||
]).then(() => {
|
||||
logger.log(`${this}: flushSync completed`);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2019 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.
|
||||
@@ -15,11 +16,9 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import Promise from 'bluebird';
|
||||
|
||||
import TestClient from '../TestClient';
|
||||
import testUtils from '../test-utils';
|
||||
import logger from '../../src/logger';
|
||||
import {TestClient} from '../TestClient';
|
||||
import * as testUtils from '../test-utils';
|
||||
import {logger} from '../../src/logger';
|
||||
|
||||
const ROOM_ID = "!room:id";
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2019 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.
|
||||
@@ -24,18 +25,14 @@ limitations under the License.
|
||||
* See also `megolm.spec.js`.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
import 'source-map-support/register';
|
||||
|
||||
// load olm before the sdk if possible
|
||||
import '../olm-loader';
|
||||
|
||||
const sdk = require("../..");
|
||||
import Promise from 'bluebird';
|
||||
const utils = require("../../lib/utils");
|
||||
const testUtils = require("../test-utils");
|
||||
const TestClient = require('../TestClient').default;
|
||||
import logger from '../../src/logger';
|
||||
import {logger} from '../../src/logger';
|
||||
import * as testUtils from "../test-utils";
|
||||
import * as utils from "../../src/utils";
|
||||
import {TestClient} from "../TestClient";
|
||||
import {CRYPTO_ENABLED} from "../../src/client";
|
||||
|
||||
let aliTestClient;
|
||||
const roomId = "!room:localhost";
|
||||
@@ -401,7 +398,7 @@ function firstSync(testClient) {
|
||||
|
||||
|
||||
describe("MatrixClient crypto", function() {
|
||||
if (!sdk.CRYPTO_ENABLED) {
|
||||
if (!CRYPTO_ENABLED) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +1,16 @@
|
||||
"use strict";
|
||||
import 'source-map-support/register';
|
||||
const sdk = require("../..");
|
||||
const HttpBackend = require("matrix-mock-request");
|
||||
const utils = require("../test-utils");
|
||||
|
||||
import Promise from 'bluebird';
|
||||
import * as utils from "../test-utils";
|
||||
import {TestClient} from "../TestClient";
|
||||
|
||||
describe("MatrixClient events", function() {
|
||||
const baseUrl = "http://localhost.or.something";
|
||||
let client;
|
||||
let httpBackend;
|
||||
const selfUserId = "@alice:localhost";
|
||||
const selfAccessToken = "aseukfgwef";
|
||||
|
||||
beforeEach(function() {
|
||||
httpBackend = new HttpBackend();
|
||||
sdk.request(httpBackend.requestFn);
|
||||
client = sdk.createClient({
|
||||
baseUrl: baseUrl,
|
||||
userId: selfUserId,
|
||||
accessToken: selfAccessToken,
|
||||
});
|
||||
const testClient = new TestClient(selfUserId, "DEVICE", selfAccessToken);
|
||||
client = testClient.client;
|
||||
httpBackend = testClient.httpBackend;
|
||||
httpBackend.when("GET", "/pushrules").respond(200, {});
|
||||
httpBackend.when("POST", "/filter").respond(200, { filter_id: "a filter id" });
|
||||
});
|
||||
@@ -162,7 +152,7 @@ describe("MatrixClient events", function() {
|
||||
});
|
||||
client.startClient();
|
||||
|
||||
httpBackend.flushAllExpected().done(function() {
|
||||
httpBackend.flushAllExpected().then(function() {
|
||||
expect(fired).toBe(true, "User.presence didn't fire.");
|
||||
done();
|
||||
});
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
"use strict";
|
||||
import 'source-map-support/register';
|
||||
import Promise from 'bluebird';
|
||||
const sdk = require("../..");
|
||||
const HttpBackend = require("matrix-mock-request");
|
||||
const utils = require("../test-utils");
|
||||
const EventTimeline = sdk.EventTimeline;
|
||||
import logger from '../../src/logger';
|
||||
import * as utils from "../test-utils";
|
||||
import {EventTimeline} from "../../src/matrix";
|
||||
import {logger} from "../../src/logger";
|
||||
import {TestClient} from "../TestClient";
|
||||
|
||||
const baseUrl = "http://localhost.or.something";
|
||||
const userId = "@alice:localhost";
|
||||
const userName = "Alice";
|
||||
const accessToken = "aseukfgwef";
|
||||
@@ -104,8 +99,9 @@ describe("getEventTimeline support", function() {
|
||||
let client;
|
||||
|
||||
beforeEach(function() {
|
||||
httpBackend = new HttpBackend();
|
||||
sdk.request(httpBackend.requestFn);
|
||||
const testClient = new TestClient(userId, "DEVICE", accessToken);
|
||||
client = testClient.client;
|
||||
httpBackend = testClient.httpBackend;
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
@@ -116,12 +112,6 @@ describe("getEventTimeline support", function() {
|
||||
});
|
||||
|
||||
it("timeline support must be enabled to work", function() {
|
||||
client = sdk.createClient({
|
||||
baseUrl: baseUrl,
|
||||
userId: userId,
|
||||
accessToken: accessToken,
|
||||
});
|
||||
|
||||
return startClient(httpBackend, client).then(function() {
|
||||
const room = client.getRoom(roomId);
|
||||
const timelineSet = room.getTimelineSets()[0];
|
||||
@@ -132,12 +122,15 @@ describe("getEventTimeline support", function() {
|
||||
});
|
||||
|
||||
it("timeline support works when enabled", function() {
|
||||
client = sdk.createClient({
|
||||
baseUrl: baseUrl,
|
||||
userId: userId,
|
||||
accessToken: accessToken,
|
||||
timelineSupport: true,
|
||||
});
|
||||
const testClient = new TestClient(
|
||||
userId,
|
||||
"DEVICE",
|
||||
accessToken,
|
||||
undefined,
|
||||
{timelineSupport: true},
|
||||
);
|
||||
client = testClient.client;
|
||||
httpBackend = testClient.httpBackend;
|
||||
|
||||
return startClient(httpBackend, client).then(() => {
|
||||
const room = client.getRoom(roomId);
|
||||
@@ -152,11 +145,7 @@ describe("getEventTimeline support", function() {
|
||||
it("scrollback should be able to scroll back to before a gappy /sync",
|
||||
function() {
|
||||
// need a client with timelineSupport disabled to make this work
|
||||
client = sdk.createClient({
|
||||
baseUrl: baseUrl,
|
||||
userId: userId,
|
||||
accessToken: accessToken,
|
||||
});
|
||||
|
||||
let room;
|
||||
|
||||
return startClient(httpBackend, client).then(function() {
|
||||
@@ -224,15 +213,15 @@ describe("MatrixClient event timelines", function() {
|
||||
let httpBackend = null;
|
||||
|
||||
beforeEach(function() {
|
||||
httpBackend = new HttpBackend();
|
||||
sdk.request(httpBackend.requestFn);
|
||||
|
||||
client = sdk.createClient({
|
||||
baseUrl: baseUrl,
|
||||
userId: userId,
|
||||
accessToken: accessToken,
|
||||
timelineSupport: true,
|
||||
});
|
||||
const testClient = new TestClient(
|
||||
userId,
|
||||
"DEVICE",
|
||||
accessToken,
|
||||
undefined,
|
||||
{timelineSupport: true},
|
||||
);
|
||||
client = testClient.client;
|
||||
httpBackend = testClient.httpBackend;
|
||||
|
||||
return startClient(httpBackend, client);
|
||||
});
|
||||
@@ -356,7 +345,7 @@ describe("MatrixClient event timelines", function() {
|
||||
.toEqual("start_token");
|
||||
// expect(tl.getPaginationToken(EventTimeline.FORWARDS))
|
||||
// .toEqual("s_5_4");
|
||||
}).done(resolve, reject);
|
||||
}).then(resolve, reject);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,39 +1,23 @@
|
||||
"use strict";
|
||||
import 'source-map-support/register';
|
||||
const sdk = require("../..");
|
||||
const HttpBackend = require("matrix-mock-request");
|
||||
const publicGlobals = require("../../lib/matrix");
|
||||
const Room = publicGlobals.Room;
|
||||
const MemoryStore = publicGlobals.MemoryStore;
|
||||
const Filter = publicGlobals.Filter;
|
||||
const utils = require("../test-utils");
|
||||
const MockStorageApi = require("../MockStorageApi");
|
||||
import * as utils from "../test-utils";
|
||||
import {CRYPTO_ENABLED} from "../../src/client";
|
||||
import {Filter, MemoryStore, Room} from "../../src/matrix";
|
||||
import {TestClient} from "../TestClient";
|
||||
|
||||
describe("MatrixClient", function() {
|
||||
const baseUrl = "http://localhost.or.something";
|
||||
let client = null;
|
||||
let httpBackend = null;
|
||||
let store = null;
|
||||
let sessionStore = null;
|
||||
const userId = "@alice:localhost";
|
||||
const accessToken = "aseukfgwef";
|
||||
|
||||
beforeEach(function() {
|
||||
httpBackend = new HttpBackend();
|
||||
store = new MemoryStore();
|
||||
|
||||
const mockStorage = new MockStorageApi();
|
||||
sessionStore = new sdk.WebStorageSessionStore(mockStorage);
|
||||
|
||||
sdk.request(httpBackend.requestFn);
|
||||
client = sdk.createClient({
|
||||
baseUrl: baseUrl,
|
||||
userId: userId,
|
||||
deviceId: "aliceDevice",
|
||||
accessToken: accessToken,
|
||||
const testClient = new TestClient(userId, "aliceDevice", accessToken, undefined, {
|
||||
store: store,
|
||||
sessionStore: sessionStore,
|
||||
});
|
||||
httpBackend = testClient.httpBackend;
|
||||
client = testClient.client;
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
@@ -181,7 +165,7 @@ describe("MatrixClient", function() {
|
||||
event_format: "client",
|
||||
});
|
||||
store.storeFilter(filter);
|
||||
client.getFilter(userId, filterId, true).done(function(gotFilter) {
|
||||
client.getFilter(userId, filterId, true).then(function(gotFilter) {
|
||||
expect(gotFilter).toEqual(filter);
|
||||
done();
|
||||
});
|
||||
@@ -202,7 +186,7 @@ describe("MatrixClient", function() {
|
||||
event_format: "client",
|
||||
});
|
||||
store.storeFilter(storeFilter);
|
||||
client.getFilter(userId, filterId, false).done(function(gotFilter) {
|
||||
client.getFilter(userId, filterId, false).then(function(gotFilter) {
|
||||
expect(gotFilter.getDefinition()).toEqual(httpFilterDefinition);
|
||||
done();
|
||||
});
|
||||
@@ -220,7 +204,7 @@ describe("MatrixClient", function() {
|
||||
httpBackend.when(
|
||||
"GET", "/user/" + encodeURIComponent(userId) + "/filter/" + filterId,
|
||||
).respond(200, httpFilterDefinition);
|
||||
client.getFilter(userId, filterId, true).done(function(gotFilter) {
|
||||
client.getFilter(userId, filterId, true).then(function(gotFilter) {
|
||||
expect(gotFilter.getDefinition()).toEqual(httpFilterDefinition);
|
||||
expect(store.getFilter(userId, filterId)).toBeTruthy();
|
||||
done();
|
||||
@@ -248,7 +232,7 @@ describe("MatrixClient", function() {
|
||||
filter_id: filterId,
|
||||
});
|
||||
|
||||
client.createFilter(filterDefinition).done(function(gotFilter) {
|
||||
client.createFilter(filterDefinition).then(function(gotFilter) {
|
||||
expect(gotFilter.getDefinition()).toEqual(filterDefinition);
|
||||
expect(store.getFilter(userId, filterId)).toEqual(gotFilter);
|
||||
done();
|
||||
@@ -295,7 +279,7 @@ describe("MatrixClient", function() {
|
||||
});
|
||||
}).respond(200, response);
|
||||
|
||||
httpBackend.flush().done(function() {
|
||||
httpBackend.flush().then(function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -303,7 +287,7 @@ describe("MatrixClient", function() {
|
||||
|
||||
|
||||
describe("downloadKeys", function() {
|
||||
if (!sdk.CRYPTO_ENABLED) {
|
||||
if (!CRYPTO_ENABLED) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
"use strict";
|
||||
import 'source-map-support/register';
|
||||
const sdk = require("../..");
|
||||
const MatrixClient = sdk.MatrixClient;
|
||||
const HttpBackend = require("matrix-mock-request");
|
||||
const utils = require("../test-utils");
|
||||
|
||||
import Promise from 'bluebird';
|
||||
import * as utils from "../test-utils";
|
||||
import HttpBackend from "matrix-mock-request";
|
||||
import {MatrixClient} from "../../src/matrix";
|
||||
import {MatrixScheduler} from "../../src/scheduler";
|
||||
import {MemoryStore} from "../../src/store/memory";
|
||||
|
||||
describe("MatrixClient opts", function() {
|
||||
const baseUrl = "http://localhost.or.something";
|
||||
@@ -73,7 +70,7 @@ describe("MatrixClient opts", function() {
|
||||
baseUrl: baseUrl,
|
||||
userId: userId,
|
||||
accessToken: accessToken,
|
||||
scheduler: new sdk.MatrixScheduler(),
|
||||
scheduler: new MatrixScheduler(),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -86,7 +83,7 @@ describe("MatrixClient opts", function() {
|
||||
httpBackend.when("PUT", "/txn1").respond(200, {
|
||||
event_id: eventId,
|
||||
});
|
||||
client.sendTextMessage("!foo:bar", "a body", "txn1").done(function(res) {
|
||||
client.sendTextMessage("!foo:bar", "a body", "txn1").then(function(res) {
|
||||
expect(res.event_id).toEqual(eventId);
|
||||
done();
|
||||
});
|
||||
@@ -126,7 +123,7 @@ describe("MatrixClient opts", function() {
|
||||
beforeEach(function() {
|
||||
client = new MatrixClient({
|
||||
request: httpBackend.requestFn,
|
||||
store: new sdk.MemoryStore(),
|
||||
store: new MemoryStore(),
|
||||
baseUrl: baseUrl,
|
||||
userId: userId,
|
||||
accessToken: accessToken,
|
||||
@@ -139,7 +136,7 @@ describe("MatrixClient opts", function() {
|
||||
errcode: "M_SOMETHING",
|
||||
error: "Ruh roh",
|
||||
});
|
||||
client.sendTextMessage("!foo:bar", "a body", "txn1").done(function(res) {
|
||||
client.sendTextMessage("!foo:bar", "a body", "txn1").then(function(res) {
|
||||
expect(false).toBe(true, "sendTextMessage resolved but shouldn't");
|
||||
}, function(err) {
|
||||
expect(err.errcode).toEqual("M_SOMETHING");
|
||||
@@ -157,16 +154,16 @@ describe("MatrixClient opts", function() {
|
||||
});
|
||||
let sentA = false;
|
||||
let sentB = false;
|
||||
client.sendTextMessage("!foo:bar", "a body", "txn1").done(function(res) {
|
||||
client.sendTextMessage("!foo:bar", "a body", "txn1").then(function(res) {
|
||||
sentA = true;
|
||||
expect(sentB).toBe(true);
|
||||
});
|
||||
client.sendTextMessage("!foo:bar", "b body", "txn2").done(function(res) {
|
||||
client.sendTextMessage("!foo:bar", "b body", "txn2").then(function(res) {
|
||||
sentB = true;
|
||||
expect(sentA).toBe(false);
|
||||
});
|
||||
httpBackend.flush("/txn2", 1).done(function() {
|
||||
httpBackend.flush("/txn1", 1).done(function() {
|
||||
httpBackend.flush("/txn2", 1).then(function() {
|
||||
httpBackend.flush("/txn1", 1).then(function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -176,7 +173,7 @@ describe("MatrixClient opts", function() {
|
||||
httpBackend.when("PUT", "/txn1").respond(200, {
|
||||
event_id: "foo",
|
||||
});
|
||||
client.sendTextMessage("!foo:bar", "a body", "txn1").done(function(res) {
|
||||
client.sendTextMessage("!foo:bar", "a body", "txn1").then(function(res) {
|
||||
expect(res.event_id).toEqual("foo");
|
||||
done();
|
||||
});
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
"use strict";
|
||||
import 'source-map-support/register';
|
||||
import Promise from 'bluebird';
|
||||
|
||||
const sdk = require("../..");
|
||||
const HttpBackend = require("matrix-mock-request");
|
||||
const EventStatus = sdk.EventStatus;
|
||||
import {EventStatus} from "../../src/matrix";
|
||||
import {MatrixScheduler} from "../../src/scheduler";
|
||||
import {Room} from "../../src/models/room";
|
||||
import {TestClient} from "../TestClient";
|
||||
|
||||
describe("MatrixClient retrying", function() {
|
||||
const baseUrl = "http://localhost.or.something";
|
||||
let client = null;
|
||||
let httpBackend = null;
|
||||
let scheduler;
|
||||
@@ -17,16 +13,17 @@ describe("MatrixClient retrying", function() {
|
||||
let room;
|
||||
|
||||
beforeEach(function() {
|
||||
httpBackend = new HttpBackend();
|
||||
sdk.request(httpBackend.requestFn);
|
||||
scheduler = new sdk.MatrixScheduler();
|
||||
client = sdk.createClient({
|
||||
baseUrl: baseUrl,
|
||||
userId: userId,
|
||||
accessToken: accessToken,
|
||||
scheduler: scheduler,
|
||||
});
|
||||
room = new sdk.Room(roomId);
|
||||
scheduler = new MatrixScheduler();
|
||||
const testClient = new TestClient(
|
||||
userId,
|
||||
"DEVICE",
|
||||
accessToken,
|
||||
undefined,
|
||||
{scheduler},
|
||||
);
|
||||
httpBackend = testClient.httpBackend;
|
||||
client = testClient.client;
|
||||
room = new Room(roomId);
|
||||
client.store.storeRoom(room);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
"use strict";
|
||||
import 'source-map-support/register';
|
||||
const sdk = require("../..");
|
||||
const EventStatus = sdk.EventStatus;
|
||||
const HttpBackend = require("matrix-mock-request");
|
||||
const utils = require("../test-utils");
|
||||
import * as utils from "../test-utils";
|
||||
import {EventStatus} from "../../src/models/event";
|
||||
import {TestClient} from "../TestClient";
|
||||
|
||||
import Promise from 'bluebird';
|
||||
|
||||
describe("MatrixClient room timelines", function() {
|
||||
const baseUrl = "http://localhost.or.something";
|
||||
let client = null;
|
||||
let httpBackend = null;
|
||||
const userId = "@alice:localhost";
|
||||
@@ -103,15 +98,17 @@ describe("MatrixClient room timelines", function() {
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
httpBackend = new HttpBackend();
|
||||
sdk.request(httpBackend.requestFn);
|
||||
client = sdk.createClient({
|
||||
baseUrl: baseUrl,
|
||||
userId: userId,
|
||||
accessToken: accessToken,
|
||||
// these tests should work with or without timelineSupport
|
||||
timelineSupport: true,
|
||||
});
|
||||
// these tests should work with or without timelineSupport
|
||||
const testClient = new TestClient(
|
||||
userId,
|
||||
"DEVICE",
|
||||
accessToken,
|
||||
undefined,
|
||||
{timelineSupport: true},
|
||||
);
|
||||
httpBackend = testClient.httpBackend;
|
||||
client = testClient.client;
|
||||
|
||||
setNextSyncData();
|
||||
httpBackend.when("GET", "/pushrules").respond(200, {});
|
||||
httpBackend.when("POST", "/filter").respond(200, { filter_id: "fid" });
|
||||
@@ -151,7 +148,7 @@ describe("MatrixClient room timelines", function() {
|
||||
expect(member.userId).toEqual(userId);
|
||||
expect(member.name).toEqual(userName);
|
||||
|
||||
httpBackend.flush("/sync", 1).done(function() {
|
||||
httpBackend.flush("/sync", 1).then(function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -177,10 +174,10 @@ describe("MatrixClient room timelines", function() {
|
||||
return;
|
||||
}
|
||||
const room = client.getRoom(roomId);
|
||||
client.sendTextMessage(roomId, "I am a fish", "txn1").done(
|
||||
client.sendTextMessage(roomId, "I am a fish", "txn1").then(
|
||||
function() {
|
||||
expect(room.timeline[1].getId()).toEqual(eventId);
|
||||
httpBackend.flush("/sync", 1).done(function() {
|
||||
httpBackend.flush("/sync", 1).then(function() {
|
||||
expect(room.timeline[1].getId()).toEqual(eventId);
|
||||
done();
|
||||
});
|
||||
@@ -210,10 +207,10 @@ describe("MatrixClient room timelines", function() {
|
||||
}
|
||||
const room = client.getRoom(roomId);
|
||||
const promise = client.sendTextMessage(roomId, "I am a fish", "txn1");
|
||||
httpBackend.flush("/sync", 1).done(function() {
|
||||
httpBackend.flush("/sync", 1).then(function() {
|
||||
expect(room.timeline.length).toEqual(2);
|
||||
httpBackend.flush("/txn1", 1);
|
||||
promise.done(function() {
|
||||
promise.then(function() {
|
||||
expect(room.timeline.length).toEqual(2);
|
||||
expect(room.timeline[1].getId()).toEqual(eventId);
|
||||
done();
|
||||
@@ -248,7 +245,7 @@ describe("MatrixClient room timelines", function() {
|
||||
const room = client.getRoom(roomId);
|
||||
expect(room.timeline.length).toEqual(1);
|
||||
|
||||
client.scrollback(room).done(function() {
|
||||
client.scrollback(room).then(function() {
|
||||
expect(room.timeline.length).toEqual(1);
|
||||
expect(room.oldState.paginationToken).toBe(null);
|
||||
|
||||
@@ -312,7 +309,7 @@ describe("MatrixClient room timelines", function() {
|
||||
// sync response
|
||||
expect(room.timeline.length).toEqual(1);
|
||||
|
||||
client.scrollback(room).done(function() {
|
||||
client.scrollback(room).then(function() {
|
||||
expect(room.timeline.length).toEqual(5);
|
||||
const joinMsg = room.timeline[0];
|
||||
expect(joinMsg.sender.name).toEqual("Old Alice");
|
||||
@@ -350,7 +347,7 @@ describe("MatrixClient room timelines", function() {
|
||||
const room = client.getRoom(roomId);
|
||||
expect(room.timeline.length).toEqual(1);
|
||||
|
||||
client.scrollback(room).done(function() {
|
||||
client.scrollback(room).then(function() {
|
||||
expect(room.timeline.length).toEqual(3);
|
||||
expect(room.timeline[0].event).toEqual(sbEvents[1]);
|
||||
expect(room.timeline[1].event).toEqual(sbEvents[0]);
|
||||
@@ -381,11 +378,11 @@ describe("MatrixClient room timelines", function() {
|
||||
const room = client.getRoom(roomId);
|
||||
expect(room.oldState.paginationToken).toBeTruthy();
|
||||
|
||||
client.scrollback(room, 1).done(function() {
|
||||
client.scrollback(room, 1).then(function() {
|
||||
expect(room.oldState.paginationToken).toEqual(sbEndTok);
|
||||
});
|
||||
|
||||
httpBackend.flush("/messages", 1).done(function() {
|
||||
httpBackend.flush("/messages", 1).then(function() {
|
||||
// still have a sync to flush
|
||||
httpBackend.flush("/sync", 1).then(() => {
|
||||
done();
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
"use strict";
|
||||
import 'source-map-support/register';
|
||||
const sdk = require("../..");
|
||||
const HttpBackend = require("matrix-mock-request");
|
||||
const utils = require("../test-utils");
|
||||
const MatrixEvent = sdk.MatrixEvent;
|
||||
const EventTimeline = sdk.EventTimeline;
|
||||
|
||||
import Promise from 'bluebird';
|
||||
import {MatrixEvent} from "../../src/models/event";
|
||||
import {EventTimeline} from "../../src/models/event-timeline";
|
||||
import * as utils from "../test-utils";
|
||||
import {TestClient} from "../TestClient";
|
||||
|
||||
describe("MatrixClient syncing", function() {
|
||||
const baseUrl = "http://localhost.or.something";
|
||||
let client = null;
|
||||
let httpBackend = null;
|
||||
const selfUserId = "@alice:localhost";
|
||||
@@ -22,13 +16,9 @@ describe("MatrixClient syncing", function() {
|
||||
const roomTwo = "!bar:localhost";
|
||||
|
||||
beforeEach(function() {
|
||||
httpBackend = new HttpBackend();
|
||||
sdk.request(httpBackend.requestFn);
|
||||
client = sdk.createClient({
|
||||
baseUrl: baseUrl,
|
||||
userId: selfUserId,
|
||||
accessToken: selfAccessToken,
|
||||
});
|
||||
const testClient = new TestClient(selfUserId, "DEVICE", selfAccessToken);
|
||||
httpBackend = testClient.httpBackend;
|
||||
client = testClient.client;
|
||||
httpBackend.when("GET", "/pushrules").respond(200, {});
|
||||
httpBackend.when("POST", "/filter").respond(200, { filter_id: "a filter id" });
|
||||
});
|
||||
@@ -51,7 +41,7 @@ describe("MatrixClient syncing", function() {
|
||||
|
||||
client.startClient();
|
||||
|
||||
httpBackend.flushAllExpected().done(function() {
|
||||
httpBackend.flushAllExpected().then(function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -65,7 +55,7 @@ describe("MatrixClient syncing", function() {
|
||||
|
||||
client.startClient();
|
||||
|
||||
httpBackend.flushAllExpected().done(function() {
|
||||
httpBackend.flushAllExpected().then(function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
Copyright 2019 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.
|
||||
@@ -14,15 +15,11 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const anotherjson = require('another-json');
|
||||
import Promise from 'bluebird';
|
||||
|
||||
const utils = require('../../lib/utils');
|
||||
const testUtils = require('../test-utils');
|
||||
const TestClient = require('../TestClient').default;
|
||||
import logger from '../../src/logger';
|
||||
import anotherjson from "another-json";
|
||||
import * as utils from "../../src/utils";
|
||||
import * as testUtils from "../test-utils";
|
||||
import {TestClient} from "../TestClient";
|
||||
import {logger} from "../../src/logger";
|
||||
|
||||
const ROOM_ID = "!room:id";
|
||||
|
||||
@@ -618,6 +615,9 @@ describe("megolm", function() {
|
||||
).respond(200, {
|
||||
event_id: '$event_id',
|
||||
});
|
||||
aliceTestClient.httpBackend.when(
|
||||
'PUT', '/sendToDevice/org.matrix.room_key.withheld/',
|
||||
).respond(200, {});
|
||||
|
||||
return Promise.all([
|
||||
aliceTestClient.client.sendTextMessage(ROOM_ID, 'test'),
|
||||
@@ -715,6 +715,9 @@ describe("megolm", function() {
|
||||
event_id: '$event_id',
|
||||
};
|
||||
});
|
||||
aliceTestClient.httpBackend.when(
|
||||
'PUT', '/sendToDevice/org.matrix.room_key.withheld/',
|
||||
).respond(200, {});
|
||||
|
||||
return Promise.all([
|
||||
aliceTestClient.client.sendTextMessage(ROOM_ID, 'test2'),
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright 2017 Vector creations Ltd
|
||||
Copyright 2019 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.
|
||||
@@ -14,7 +15,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import logger from '../src/logger';
|
||||
import {logger} from '../src/logger';
|
||||
|
||||
// try to load the olm library.
|
||||
try {
|
||||
|
||||
+32
-36
@@ -1,12 +1,8 @@
|
||||
"use strict";
|
||||
import Promise from 'bluebird';
|
||||
|
||||
// load olm before the sdk if possible
|
||||
import './olm-loader';
|
||||
|
||||
import logger from '../src/logger';
|
||||
import sdk from '..';
|
||||
const MatrixEvent = sdk.MatrixEvent;
|
||||
import {logger} from '../src/logger';
|
||||
import {MatrixEvent} from "../src/models/event";
|
||||
|
||||
/**
|
||||
* Return a promise that is resolved when the client next emits a
|
||||
@@ -15,7 +11,7 @@ const MatrixEvent = sdk.MatrixEvent;
|
||||
* @param {Number=} count Number of syncs to wait for (default 1)
|
||||
* @return {Promise} Resolves once the client has emitted a SYNCING event
|
||||
*/
|
||||
module.exports.syncPromise = function(client, count) {
|
||||
export function syncPromise(client, count) {
|
||||
if (count === undefined) {
|
||||
count = 1;
|
||||
}
|
||||
@@ -26,7 +22,7 @@ module.exports.syncPromise = function(client, count) {
|
||||
const p = new Promise((resolve, reject) => {
|
||||
const cb = (state) => {
|
||||
logger.log(`${Date.now()} syncPromise(${count}): ${state}`);
|
||||
if (state == 'SYNCING') {
|
||||
if (state === 'SYNCING') {
|
||||
resolve();
|
||||
} else {
|
||||
client.once('sync', cb);
|
||||
@@ -36,9 +32,9 @@ module.exports.syncPromise = function(client, count) {
|
||||
});
|
||||
|
||||
return p.then(() => {
|
||||
return module.exports.syncPromise(client, count-1);
|
||||
return syncPromise(client, count-1);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a spy for an object and automatically spy its methods.
|
||||
@@ -46,7 +42,7 @@ module.exports.syncPromise = function(client, count) {
|
||||
* @param {string} name The name of the class
|
||||
* @return {Object} An instantiated object with spied methods/properties.
|
||||
*/
|
||||
module.exports.mock = function(constr, name) {
|
||||
export function mock(constr, name) {
|
||||
// Based on
|
||||
// http://eclipsesource.com/blogs/2014/03/27/mocks-in-jasmine-tests/
|
||||
const HelperConstr = new Function(); // jshint ignore:line
|
||||
@@ -67,7 +63,7 @@ module.exports.mock = function(constr, name) {
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an Event.
|
||||
@@ -80,7 +76,7 @@ module.exports.mock = function(constr, name) {
|
||||
* @param {boolean} opts.event True to make a MatrixEvent.
|
||||
* @return {Object} a JSON object representing this event.
|
||||
*/
|
||||
module.exports.mkEvent = function(opts) {
|
||||
export function mkEvent(opts) {
|
||||
if (!opts.type || !opts.content) {
|
||||
throw new Error("Missing .type or .content =>" + JSON.stringify(opts));
|
||||
}
|
||||
@@ -99,14 +95,14 @@ module.exports.mkEvent = function(opts) {
|
||||
event.state_key = "";
|
||||
}
|
||||
return opts.event ? new MatrixEvent(event) : event;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an m.presence event.
|
||||
* @param {Object} opts Values for the presence.
|
||||
* @return {Object|MatrixEvent} The event
|
||||
*/
|
||||
module.exports.mkPresence = function(opts) {
|
||||
export function mkPresence(opts) {
|
||||
if (!opts.user) {
|
||||
throw new Error("Missing user");
|
||||
}
|
||||
@@ -122,7 +118,7 @@ module.exports.mkPresence = function(opts) {
|
||||
},
|
||||
};
|
||||
return opts.event ? new MatrixEvent(event) : event;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an m.room.member event.
|
||||
@@ -137,7 +133,7 @@ module.exports.mkPresence = function(opts) {
|
||||
* @param {boolean} opts.event True to make a MatrixEvent.
|
||||
* @return {Object|MatrixEvent} The event
|
||||
*/
|
||||
module.exports.mkMembership = function(opts) {
|
||||
export function mkMembership(opts) {
|
||||
opts.type = "m.room.member";
|
||||
if (!opts.skey) {
|
||||
opts.skey = opts.sender || opts.user;
|
||||
@@ -154,8 +150,8 @@ module.exports.mkMembership = function(opts) {
|
||||
if (opts.url) {
|
||||
opts.content.avatar_url = opts.url;
|
||||
}
|
||||
return module.exports.mkEvent(opts);
|
||||
};
|
||||
return mkEvent(opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an m.room.message event.
|
||||
@@ -166,7 +162,7 @@ module.exports.mkMembership = function(opts) {
|
||||
* @param {boolean} opts.event True to make a MatrixEvent.
|
||||
* @return {Object|MatrixEvent} The event
|
||||
*/
|
||||
module.exports.mkMessage = function(opts) {
|
||||
export function mkMessage(opts) {
|
||||
opts.type = "m.room.message";
|
||||
if (!opts.msg) {
|
||||
opts.msg = "Random->" + Math.random();
|
||||
@@ -178,8 +174,8 @@ module.exports.mkMessage = function(opts) {
|
||||
msgtype: "m.text",
|
||||
body: opts.msg,
|
||||
};
|
||||
return module.exports.mkEvent(opts);
|
||||
};
|
||||
return mkEvent(opts);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@@ -187,10 +183,10 @@ module.exports.mkMessage = function(opts) {
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
module.exports.MockStorageApi = function() {
|
||||
export function MockStorageApi() {
|
||||
this.data = {};
|
||||
};
|
||||
module.exports.MockStorageApi.prototype = {
|
||||
}
|
||||
MockStorageApi.prototype = {
|
||||
get length() {
|
||||
return Object.keys(this.data).length;
|
||||
},
|
||||
@@ -215,7 +211,7 @@ module.exports.MockStorageApi.prototype = {
|
||||
* @param {MatrixEvent} event
|
||||
* @returns {Promise} promise which resolves (to `event`) when the event has been decrypted
|
||||
*/
|
||||
module.exports.awaitDecryption = function(event) {
|
||||
export function awaitDecryption(event) {
|
||||
if (!event.isBeingDecrypted()) {
|
||||
return Promise.resolve(event);
|
||||
}
|
||||
@@ -228,19 +224,19 @@ module.exports.awaitDecryption = function(event) {
|
||||
resolve(ev);
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
const HttpResponse = module.exports.HttpResponse = function(
|
||||
export function HttpResponse(
|
||||
httpLookups, acceptKeepalives, ignoreUnhandledSync,
|
||||
) {
|
||||
this.httpLookups = httpLookups;
|
||||
this.acceptKeepalives = acceptKeepalives === undefined ? true : acceptKeepalives;
|
||||
this.ignoreUnhandledSync = ignoreUnhandledSync;
|
||||
this.pendingLookup = null;
|
||||
};
|
||||
}
|
||||
|
||||
HttpResponse.prototype.request = function HttpResponse(
|
||||
HttpResponse.prototype.request = function(
|
||||
cb, method, path, qp, data, prefix,
|
||||
) {
|
||||
if (path === HttpResponse.KEEP_ALIVE_PATH && this.acceptKeepalives) {
|
||||
@@ -256,7 +252,7 @@ HttpResponse.prototype.request = function HttpResponse(
|
||||
if (!next) { // no more things to return
|
||||
if (method === "GET" && path === "/sync" && this.ignoreUnhandledSync) {
|
||||
logger.log("MatrixClient[UT] Ignoring.");
|
||||
return Promise.defer().promise;
|
||||
return new Promise(() => {});
|
||||
}
|
||||
if (this.pendingLookup) {
|
||||
if (this.pendingLookup.method === method
|
||||
@@ -271,7 +267,7 @@ HttpResponse.prototype.request = function HttpResponse(
|
||||
);
|
||||
}
|
||||
this.pendingLookup = {
|
||||
promise: Promise.defer().promise,
|
||||
promise: new Promise(() => {}),
|
||||
method: method,
|
||||
path: path,
|
||||
};
|
||||
@@ -308,10 +304,10 @@ HttpResponse.prototype.request = function HttpResponse(
|
||||
} else if (method === "GET" && path === "/sync" && this.ignoreUnhandledSync) {
|
||||
logger.log("MatrixClient[UT] Ignoring.");
|
||||
this.httpLookups.unshift(next);
|
||||
return Promise.defer().promise;
|
||||
return new Promise(() => {});
|
||||
}
|
||||
expect(true).toBe(false, "Expected different request. " + logLine);
|
||||
return Promise.defer().promise;
|
||||
return new Promise(() => {});
|
||||
};
|
||||
|
||||
HttpResponse.KEEP_ALIVE_PATH = "/_matrix/client/versions";
|
||||
@@ -353,7 +349,7 @@ HttpResponse.defaultResponses = function(userId) {
|
||||
];
|
||||
};
|
||||
|
||||
module.exports.setHttpResponses = function setHttpResponses(
|
||||
export function setHttpResponses(
|
||||
client, responses, acceptKeepalives, ignoreUnhandledSyncs,
|
||||
) {
|
||||
const httpResponseObj = new HttpResponse(
|
||||
@@ -369,4 +365,4 @@ module.exports.setHttpResponses = function setHttpResponses(
|
||||
client._http.authedRequestWithPrefix.mockImplementation(httpReq);
|
||||
client._http.requestWithPrefix.mockImplementation(httpReq);
|
||||
client._http.request.mockImplementation(httpReq);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2019 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.
|
||||
@@ -13,16 +14,10 @@ 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.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
import 'source-map-support/register';
|
||||
import Promise from 'bluebird';
|
||||
const sdk = require("../..");
|
||||
|
||||
const AutoDiscovery = sdk.AutoDiscovery;
|
||||
|
||||
import MockHttpBackend from "matrix-mock-request";
|
||||
|
||||
import * as sdk from "../../src";
|
||||
import {AutoDiscovery} from "../../src/autodiscovery";
|
||||
|
||||
describe("AutoDiscovery", function() {
|
||||
let httpBackend = null;
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"use strict";
|
||||
import 'source-map-support/register';
|
||||
const ContentRepo = require("../../lib/content-repo");
|
||||
import {getHttpUriForMxc, getIdenticonUri} from "../../src/content-repo";
|
||||
|
||||
describe("ContentRepo", function() {
|
||||
const baseUrl = "https://my.home.server";
|
||||
@@ -9,7 +7,7 @@ describe("ContentRepo", function() {
|
||||
it("should do nothing to HTTP URLs when allowing direct links", function() {
|
||||
const httpUrl = "http://example.com/image.jpeg";
|
||||
expect(
|
||||
ContentRepo.getHttpUriForMxc(
|
||||
getHttpUriForMxc(
|
||||
baseUrl, httpUrl, undefined, undefined, undefined, true,
|
||||
),
|
||||
).toEqual(httpUrl);
|
||||
@@ -17,25 +15,25 @@ describe("ContentRepo", function() {
|
||||
|
||||
it("should return the empty string HTTP URLs by default", function() {
|
||||
const httpUrl = "http://example.com/image.jpeg";
|
||||
expect(ContentRepo.getHttpUriForMxc(baseUrl, httpUrl)).toEqual("");
|
||||
expect(getHttpUriForMxc(baseUrl, httpUrl)).toEqual("");
|
||||
});
|
||||
|
||||
it("should return a download URL if no width/height/resize are specified",
|
||||
function() {
|
||||
const mxcUri = "mxc://server.name/resourceid";
|
||||
expect(ContentRepo.getHttpUriForMxc(baseUrl, mxcUri)).toEqual(
|
||||
expect(getHttpUriForMxc(baseUrl, mxcUri)).toEqual(
|
||||
baseUrl + "/_matrix/media/r0/download/server.name/resourceid",
|
||||
);
|
||||
});
|
||||
|
||||
it("should return the empty string for null input", function() {
|
||||
expect(ContentRepo.getHttpUriForMxc(null)).toEqual("");
|
||||
expect(getHttpUriForMxc(null)).toEqual("");
|
||||
});
|
||||
|
||||
it("should return a thumbnail URL if a width/height/resize is specified",
|
||||
function() {
|
||||
const mxcUri = "mxc://server.name/resourceid";
|
||||
expect(ContentRepo.getHttpUriForMxc(baseUrl, mxcUri, 32, 64, "crop")).toEqual(
|
||||
expect(getHttpUriForMxc(baseUrl, mxcUri, 32, 64, "crop")).toEqual(
|
||||
baseUrl + "/_matrix/media/r0/thumbnail/server.name/resourceid" +
|
||||
"?width=32&height=64&method=crop",
|
||||
);
|
||||
@@ -44,7 +42,7 @@ describe("ContentRepo", function() {
|
||||
it("should put fragments from mxc:// URIs after any query parameters",
|
||||
function() {
|
||||
const mxcUri = "mxc://server.name/resourceid#automade";
|
||||
expect(ContentRepo.getHttpUriForMxc(baseUrl, mxcUri, 32)).toEqual(
|
||||
expect(getHttpUriForMxc(baseUrl, mxcUri, 32)).toEqual(
|
||||
baseUrl + "/_matrix/media/r0/thumbnail/server.name/resourceid" +
|
||||
"?width=32#automade",
|
||||
);
|
||||
@@ -53,7 +51,7 @@ describe("ContentRepo", function() {
|
||||
it("should put fragments from mxc:// URIs at the end of the HTTP URI",
|
||||
function() {
|
||||
const mxcUri = "mxc://server.name/resourceid#automade";
|
||||
expect(ContentRepo.getHttpUriForMxc(baseUrl, mxcUri)).toEqual(
|
||||
expect(getHttpUriForMxc(baseUrl, mxcUri)).toEqual(
|
||||
baseUrl + "/_matrix/media/r0/download/server.name/resourceid#automade",
|
||||
);
|
||||
});
|
||||
@@ -61,25 +59,25 @@ describe("ContentRepo", function() {
|
||||
|
||||
describe("getIdenticonUri", function() {
|
||||
it("should do nothing for null input", function() {
|
||||
expect(ContentRepo.getIdenticonUri(null)).toEqual(null);
|
||||
expect(getIdenticonUri(null)).toEqual(null);
|
||||
});
|
||||
|
||||
it("should set w/h by default to 96", function() {
|
||||
expect(ContentRepo.getIdenticonUri(baseUrl, "foobar")).toEqual(
|
||||
expect(getIdenticonUri(baseUrl, "foobar")).toEqual(
|
||||
baseUrl + "/_matrix/media/unstable/identicon/foobar" +
|
||||
"?width=96&height=96",
|
||||
);
|
||||
});
|
||||
|
||||
it("should be able to set custom w/h", function() {
|
||||
expect(ContentRepo.getIdenticonUri(baseUrl, "foobar", 32, 64)).toEqual(
|
||||
expect(getIdenticonUri(baseUrl, "foobar", 32, 64)).toEqual(
|
||||
baseUrl + "/_matrix/media/unstable/identicon/foobar" +
|
||||
"?width=32&height=64",
|
||||
);
|
||||
});
|
||||
|
||||
it("should URL encode the identicon string", function() {
|
||||
expect(ContentRepo.getIdenticonUri(baseUrl, "foo#bar", 32, 64)).toEqual(
|
||||
expect(getIdenticonUri(baseUrl, "foo#bar", 32, 64)).toEqual(
|
||||
baseUrl + "/_matrix/media/unstable/identicon/foo%23bar" +
|
||||
"?width=32&height=64",
|
||||
);
|
||||
|
||||
+25
-50
@@ -1,27 +1,20 @@
|
||||
import 'source-map-support/register';
|
||||
|
||||
import '../olm-loader';
|
||||
|
||||
import Crypto from '../../lib/crypto';
|
||||
|
||||
import WebStorageSessionStore from '../../lib/store/session/webstorage';
|
||||
import MemoryCryptoStore from '../../lib/crypto/store/memory-crypto-store.js';
|
||||
import MockStorageApi from '../MockStorageApi';
|
||||
import TestClient from '../TestClient';
|
||||
import {MatrixEvent} from '../../lib/models/event';
|
||||
import Room from '../../lib/models/room';
|
||||
import olmlib from '../../lib/crypto/olmlib';
|
||||
|
||||
const EventEmitter = require("events").EventEmitter;
|
||||
|
||||
const sdk = require("../..");
|
||||
import {Crypto} from "../../src/crypto";
|
||||
import {WebStorageSessionStore} from "../../src/store/session/webstorage";
|
||||
import {MemoryCryptoStore} from "../../src/crypto/store/memory-crypto-store";
|
||||
import {MockStorageApi} from "../MockStorageApi";
|
||||
import {TestClient} from "../TestClient";
|
||||
import {MatrixEvent} from "../../src/models/event";
|
||||
import {Room} from "../../src/models/room";
|
||||
import * as olmlib from "../../src/crypto/olmlib";
|
||||
import {sleep} from "../../src/utils";
|
||||
import {EventEmitter} from "events";
|
||||
import {CRYPTO_ENABLED} from "../../src/client";
|
||||
|
||||
const Olm = global.Olm;
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
describe("Crypto", function() {
|
||||
if (!sdk.CRYPTO_ENABLED) {
|
||||
if (!CRYPTO_ENABLED) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -270,7 +263,8 @@ describe("Crypto", function() {
|
||||
await bobDecryptor.onRoomKeyEvent(ksEvent);
|
||||
await eventPromise;
|
||||
expect(events[0].getContent().msgtype).not.toBe("m.bad.encrypted");
|
||||
// the room key request should be gone since we've now decypted everything
|
||||
await sleep(1);
|
||||
// the room key request should be gone since we've now decrypted everything
|
||||
expect(await cryptoStore.getOutgoingRoomKeyRequest(roomKeyRequestBody))
|
||||
.toBeFalsy();
|
||||
},
|
||||
@@ -301,6 +295,8 @@ describe("Crypto", function() {
|
||||
});
|
||||
|
||||
it("uses a new txnid for re-requesting keys", async function() {
|
||||
jest.useFakeTimers();
|
||||
|
||||
const event = new MatrixEvent({
|
||||
sender: "@bob:example.com",
|
||||
room_id: "!someroom",
|
||||
@@ -310,52 +306,31 @@ describe("Crypto", function() {
|
||||
sender_key: "senderkey",
|
||||
},
|
||||
});
|
||||
/* return a promise and a function. When the function is called,
|
||||
* the promise will be resolved.
|
||||
*/
|
||||
function awaitFunctionCall() {
|
||||
let func;
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
func = function(...args) {
|
||||
resolve(args);
|
||||
return new Promise((resolve, reject) => {
|
||||
// give us some time to process the result before
|
||||
// continuing
|
||||
global.setTimeout(resolve, 1);
|
||||
});
|
||||
};
|
||||
});
|
||||
return {func, promise};
|
||||
}
|
||||
|
||||
// replace Alice's sendToDevice function with a mock
|
||||
aliceClient.sendToDevice = jest.fn().mockResolvedValue(undefined);
|
||||
aliceClient.startClient();
|
||||
|
||||
let promise;
|
||||
// make a room key request, and record the transaction ID for the
|
||||
// sendToDevice call
|
||||
({promise, func: aliceClient.sendToDevice} = awaitFunctionCall());
|
||||
await aliceClient.cancelAndResendEventRoomKeyRequest(event);
|
||||
jest.runAllTimers();
|
||||
let args = await promise;
|
||||
const txnId = args[2];
|
||||
jest.runAllTimers();
|
||||
await Promise.resolve();
|
||||
expect(aliceClient.sendToDevice).toBeCalledTimes(1);
|
||||
const txnId = aliceClient.sendToDevice.mock.calls[0][2];
|
||||
|
||||
// give the room key request manager time to update the state
|
||||
// of the request
|
||||
await Promise.resolve();
|
||||
|
||||
// cancel and resend the room key request
|
||||
({promise, func: aliceClient.sendToDevice} = awaitFunctionCall());
|
||||
await aliceClient.cancelAndResendEventRoomKeyRequest(event);
|
||||
jest.runAllTimers();
|
||||
await Promise.resolve();
|
||||
// cancelAndResend will call sendToDevice twice:
|
||||
// the first call to sendToDevice will be the cancellation
|
||||
args = await promise;
|
||||
// the second call to sendToDevice will be the key request
|
||||
({promise, func: aliceClient.sendToDevice} = awaitFunctionCall());
|
||||
jest.runAllTimers();
|
||||
args = await promise;
|
||||
jest.runAllTimers();
|
||||
expect(args[2]).not.toBe(txnId);
|
||||
expect(aliceClient.sendToDevice).toBeCalledTimes(3);
|
||||
expect(aliceClient.sendToDevice.mock.calls[2][2]).not.toBe(txnId);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2018, 2019 New Vector Ltd
|
||||
Copyright 2019 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.
|
||||
@@ -15,12 +16,10 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import DeviceList from '../../../lib/crypto/DeviceList';
|
||||
import MemoryCryptoStore from '../../../lib/crypto/store/memory-crypto-store.js';
|
||||
import utils from '../../../lib/utils';
|
||||
import logger from '../../../src/logger';
|
||||
|
||||
import Promise from 'bluebird';
|
||||
import {logger} from "../../../src/logger";
|
||||
import * as utils from "../../../src/utils";
|
||||
import {MemoryCryptoStore} from "../../../src/crypto/store/memory-crypto-store";
|
||||
import {DeviceList} from "../../../src/crypto/DeviceList";
|
||||
|
||||
const signedDeviceList = {
|
||||
"failures": {},
|
||||
@@ -87,7 +86,7 @@ describe('DeviceList', function() {
|
||||
|
||||
dl.startTrackingDeviceList('@test1:sw1v.org');
|
||||
|
||||
const queryDefer1 = Promise.defer();
|
||||
const queryDefer1 = utils.defer();
|
||||
downloadSpy.mockReturnValue(queryDefer1.promise);
|
||||
|
||||
const prom1 = dl.refreshOutdatedDeviceLists();
|
||||
@@ -106,7 +105,7 @@ describe('DeviceList', function() {
|
||||
|
||||
dl.startTrackingDeviceList('@test1:sw1v.org');
|
||||
|
||||
const queryDefer1 = Promise.defer();
|
||||
const queryDefer1 = utils.defer();
|
||||
downloadSpy.mockReturnValue(queryDefer1.promise);
|
||||
|
||||
const prom1 = dl.refreshOutdatedDeviceLists();
|
||||
@@ -114,7 +113,7 @@ describe('DeviceList', function() {
|
||||
downloadSpy.mockReset();
|
||||
|
||||
// outdated notif arrives while the request is in flight.
|
||||
const queryDefer2 = Promise.defer();
|
||||
const queryDefer2 = utils.defer();
|
||||
downloadSpy.mockReturnValue(queryDefer2.promise);
|
||||
|
||||
dl.invalidateUserDeviceList('@test1:sw1v.org');
|
||||
@@ -134,7 +133,7 @@ describe('DeviceList', function() {
|
||||
logger.log("Creating new devicelist to simulate app reload");
|
||||
downloadSpy.mockReset();
|
||||
const dl2 = createTestDeviceList();
|
||||
const queryDefer3 = Promise.defer();
|
||||
const queryDefer3 = utils.defer();
|
||||
downloadSpy.mockReturnValue(queryDefer3.promise);
|
||||
|
||||
const prom3 = dl2.refreshOutdatedDeviceLists();
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import '../../../olm-loader';
|
||||
import * as algorithms from "../../../../src/crypto/algorithms";
|
||||
import {MemoryCryptoStore} from "../../../../src/crypto/store/memory-crypto-store";
|
||||
import {MockStorageApi} from "../../../MockStorageApi";
|
||||
import * as testUtils from "../../../test-utils";
|
||||
import {OlmDevice} from "../../../../src/crypto/OlmDevice";
|
||||
import {Crypto} from "../../../../src/crypto";
|
||||
import {logger} from "../../../../src/logger";
|
||||
import {MatrixEvent} from "../../../../src/models/event";
|
||||
import {TestClient} from "../../../TestClient";
|
||||
import {Room} from "../../../../src/models/room";
|
||||
import * as olmlib from "../../../../src/crypto/olmlib";
|
||||
|
||||
import Promise from 'bluebird';
|
||||
|
||||
import sdk from '../../../..';
|
||||
import algorithms from '../../../../lib/crypto/algorithms';
|
||||
import MemoryCryptoStore from '../../../../lib/crypto/store/memory-crypto-store.js';
|
||||
import MockStorageApi from '../../../MockStorageApi';
|
||||
import testUtils from '../../../test-utils';
|
||||
import OlmDevice from '../../../../lib/crypto/OlmDevice';
|
||||
import Crypto from '../../../../lib/crypto';
|
||||
import logger from '../../../../src/logger';
|
||||
|
||||
const MatrixEvent = sdk.MatrixEvent;
|
||||
const MegolmDecryption = algorithms.DECRYPTION_CLASSES['m.megolm.v1.aes-sha2'];
|
||||
const MegolmEncryption = algorithms.ENCRYPTION_CLASSES['m.megolm.v1.aes-sha2'];
|
||||
|
||||
@@ -56,7 +55,7 @@ describe("MegolmDecryption", function() {
|
||||
mockOlmLib = {};
|
||||
mockOlmLib.ensureOlmSessionsForDevices = jest.fn();
|
||||
mockOlmLib.encryptMessageForDevice =
|
||||
jest.fn().mockReturnValue(Promise.resolve());
|
||||
jest.fn().mockResolvedValue(undefined);
|
||||
megolmDecryption.olmlib = mockOlmLib;
|
||||
});
|
||||
|
||||
@@ -136,11 +135,11 @@ describe("MegolmDecryption", function() {
|
||||
const deviceInfo = {};
|
||||
mockCrypto.getStoredDevice.mockReturnValue(deviceInfo);
|
||||
|
||||
mockOlmLib.ensureOlmSessionsForDevices.mockReturnValue(
|
||||
Promise.resolve({'@alice:foo': {'alidevice': {
|
||||
mockOlmLib.ensureOlmSessionsForDevices.mockResolvedValue({
|
||||
'@alice:foo': {'alidevice': {
|
||||
sessionId: 'alisession',
|
||||
}}}),
|
||||
);
|
||||
}},
|
||||
});
|
||||
|
||||
const awaitEncryptForDevice = new Promise((res, rej) => {
|
||||
mockOlmLib.encryptMessageForDevice.mockImplementation(() => {
|
||||
@@ -282,7 +281,7 @@ describe("MegolmDecryption", function() {
|
||||
},
|
||||
},
|
||||
}));
|
||||
mockBaseApis.sendToDevice = jest.fn().mockReturnValue(Promise.resolve());
|
||||
mockBaseApis.sendToDevice = jest.fn().mockResolvedValue(undefined);
|
||||
|
||||
mockCrypto.downloadKeys.mockReturnValue(Promise.resolve({
|
||||
'@alice:home.server': {
|
||||
@@ -344,4 +343,346 @@ describe("MegolmDecryption", function() {
|
||||
expect(ct2.session_id).toEqual(ct1.session_id);
|
||||
});
|
||||
});
|
||||
|
||||
it("notifies devices that have been blocked", async function() {
|
||||
const aliceClient = (new TestClient(
|
||||
"@alice:example.com", "alicedevice",
|
||||
)).client;
|
||||
const bobClient1 = (new TestClient(
|
||||
"@bob:example.com", "bobdevice1",
|
||||
)).client;
|
||||
const bobClient2 = (new TestClient(
|
||||
"@bob:example.com", "bobdevice2",
|
||||
)).client;
|
||||
await Promise.all([
|
||||
aliceClient.initCrypto(),
|
||||
bobClient1.initCrypto(),
|
||||
bobClient2.initCrypto(),
|
||||
]);
|
||||
const aliceDevice = aliceClient._crypto._olmDevice;
|
||||
const bobDevice1 = bobClient1._crypto._olmDevice;
|
||||
const bobDevice2 = bobClient2._crypto._olmDevice;
|
||||
|
||||
const encryptionCfg = {
|
||||
"algorithm": "m.megolm.v1.aes-sha2",
|
||||
};
|
||||
const roomId = "!someroom";
|
||||
const room = new Room(roomId, aliceClient, "@alice:example.com", {});
|
||||
room.getEncryptionTargetMembers = async function() {
|
||||
return [{userId: "@bob:example.com"}];
|
||||
};
|
||||
room.setBlacklistUnverifiedDevices(true);
|
||||
aliceClient.store.storeRoom(room);
|
||||
await aliceClient.setRoomEncryption(roomId, encryptionCfg);
|
||||
|
||||
const BOB_DEVICES = {
|
||||
bobdevice1: {
|
||||
user_id: "@bob:example.com",
|
||||
device_id: "bobdevice1",
|
||||
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
|
||||
keys: {
|
||||
"ed25519:Dynabook": bobDevice1.deviceEd25519Key,
|
||||
"curve25519:Dynabook": bobDevice1.deviceCurve25519Key,
|
||||
},
|
||||
verified: 0,
|
||||
},
|
||||
bobdevice2: {
|
||||
user_id: "@bob:example.com",
|
||||
device_id: "bobdevice2",
|
||||
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
|
||||
keys: {
|
||||
"ed25519:Dynabook": bobDevice2.deviceEd25519Key,
|
||||
"curve25519:Dynabook": bobDevice2.deviceCurve25519Key,
|
||||
},
|
||||
verified: -1,
|
||||
},
|
||||
};
|
||||
|
||||
aliceClient._crypto._deviceList.storeDevicesForUser(
|
||||
"@bob:example.com", BOB_DEVICES,
|
||||
);
|
||||
aliceClient._crypto._deviceList.downloadKeys = async function(userIds) {
|
||||
return this._getDevicesFromStore(userIds);
|
||||
};
|
||||
|
||||
let run = false;
|
||||
aliceClient.sendToDevice = async (msgtype, contentMap) => {
|
||||
run = true;
|
||||
expect(msgtype).toBe("org.matrix.room_key.withheld");
|
||||
delete contentMap["@bob:example.com"].bobdevice1.session_id;
|
||||
delete contentMap["@bob:example.com"].bobdevice2.session_id;
|
||||
expect(contentMap).toStrictEqual({
|
||||
'@bob:example.com': {
|
||||
bobdevice1: {
|
||||
algorithm: "m.megolm.v1.aes-sha2",
|
||||
room_id: roomId,
|
||||
code: 'm.unverified',
|
||||
reason:
|
||||
'The sender has disabled encrypting to unverified devices.',
|
||||
sender_key: aliceDevice.deviceCurve25519Key,
|
||||
},
|
||||
bobdevice2: {
|
||||
algorithm: "m.megolm.v1.aes-sha2",
|
||||
room_id: roomId,
|
||||
code: 'm.blacklisted',
|
||||
reason: 'The sender has blocked you.',
|
||||
sender_key: aliceDevice.deviceCurve25519Key,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const event = new MatrixEvent({
|
||||
type: "m.room.message",
|
||||
sender: "@alice:example.com",
|
||||
room_id: roomId,
|
||||
event_id: "$event",
|
||||
content: {
|
||||
msgtype: "m.text",
|
||||
body: "secret",
|
||||
},
|
||||
});
|
||||
await aliceClient._crypto.encryptEvent(event, room);
|
||||
|
||||
expect(run).toBe(true);
|
||||
|
||||
aliceClient.stopClient();
|
||||
bobClient1.stopClient();
|
||||
bobClient2.stopClient();
|
||||
});
|
||||
|
||||
it("notifies devices when unable to create olm session", async function() {
|
||||
const aliceClient = (new TestClient(
|
||||
"@alice:example.com", "alicedevice",
|
||||
)).client;
|
||||
const bobClient = (new TestClient(
|
||||
"@bob:example.com", "bobdevice",
|
||||
)).client;
|
||||
await Promise.all([
|
||||
aliceClient.initCrypto(),
|
||||
bobClient.initCrypto(),
|
||||
]);
|
||||
const aliceDevice = aliceClient._crypto._olmDevice;
|
||||
const bobDevice = bobClient._crypto._olmDevice;
|
||||
|
||||
const encryptionCfg = {
|
||||
"algorithm": "m.megolm.v1.aes-sha2",
|
||||
};
|
||||
const roomId = "!someroom";
|
||||
const aliceRoom = new Room(roomId, aliceClient, "@alice:example.com", {});
|
||||
const bobRoom = new Room(roomId, bobClient, "@bob:example.com", {});
|
||||
aliceClient.store.storeRoom(aliceRoom);
|
||||
bobClient.store.storeRoom(bobRoom);
|
||||
await aliceClient.setRoomEncryption(roomId, encryptionCfg);
|
||||
await bobClient.setRoomEncryption(roomId, encryptionCfg);
|
||||
|
||||
aliceRoom.getEncryptionTargetMembers = async () => {
|
||||
return [
|
||||
{
|
||||
userId: "@alice:example.com",
|
||||
membership: "join",
|
||||
},
|
||||
{
|
||||
userId: "@bob:example.com",
|
||||
membership: "join",
|
||||
},
|
||||
];
|
||||
};
|
||||
const BOB_DEVICES = {
|
||||
bobdevice: {
|
||||
user_id: "@bob:example.com",
|
||||
device_id: "bobdevice",
|
||||
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
|
||||
keys: {
|
||||
"ed25519:bobdevice": bobDevice.deviceEd25519Key,
|
||||
"curve25519:bobdevice": bobDevice.deviceCurve25519Key,
|
||||
},
|
||||
known: true,
|
||||
verified: 1,
|
||||
},
|
||||
};
|
||||
|
||||
aliceClient._crypto._deviceList.storeDevicesForUser(
|
||||
"@bob:example.com", BOB_DEVICES,
|
||||
);
|
||||
aliceClient._crypto._deviceList.downloadKeys = async function(userIds) {
|
||||
return this._getDevicesFromStore(userIds);
|
||||
};
|
||||
|
||||
aliceClient.claimOneTimeKeys = async () => {
|
||||
// Bob has no one-time keys
|
||||
return {
|
||||
one_time_keys: {},
|
||||
};
|
||||
};
|
||||
|
||||
let run = false;
|
||||
aliceClient.sendToDevice = async (msgtype, contentMap) => {
|
||||
run = true;
|
||||
expect(msgtype).toBe("org.matrix.room_key.withheld");
|
||||
expect(contentMap).toStrictEqual({
|
||||
'@bob:example.com': {
|
||||
bobdevice: {
|
||||
algorithm: "m.megolm.v1.aes-sha2",
|
||||
code: 'm.no_olm',
|
||||
reason: 'Unable to establish a secure channel.',
|
||||
sender_key: aliceDevice.deviceCurve25519Key,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const event = new MatrixEvent({
|
||||
type: "m.room.message",
|
||||
sender: "@alice:example.com",
|
||||
room_id: roomId,
|
||||
event_id: "$event",
|
||||
content: {},
|
||||
});
|
||||
await aliceClient._crypto.encryptEvent(event, aliceRoom);
|
||||
|
||||
expect(run).toBe(true);
|
||||
});
|
||||
|
||||
it("throws an error describing why it doesn't have a key", async function() {
|
||||
const aliceClient = (new TestClient(
|
||||
"@alice:example.com", "alicedevice",
|
||||
)).client;
|
||||
const bobClient = (new TestClient(
|
||||
"@bob:example.com", "bobdevice",
|
||||
)).client;
|
||||
await Promise.all([
|
||||
aliceClient.initCrypto(),
|
||||
bobClient.initCrypto(),
|
||||
]);
|
||||
const bobDevice = bobClient._crypto._olmDevice;
|
||||
|
||||
const roomId = "!someroom";
|
||||
|
||||
aliceClient._crypto._onToDeviceEvent(new MatrixEvent({
|
||||
type: "org.matrix.room_key.withheld",
|
||||
sender: "@bob:example.com",
|
||||
content: {
|
||||
algorithm: "m.megolm.v1.aes-sha2",
|
||||
room_id: roomId,
|
||||
session_id: "session_id",
|
||||
sender_key: bobDevice.deviceCurve25519Key,
|
||||
code: "m.blacklisted",
|
||||
reason: "You have been blocked",
|
||||
},
|
||||
}));
|
||||
|
||||
await expect(aliceClient._crypto.decryptEvent(new MatrixEvent({
|
||||
type: "m.room.encrypted",
|
||||
sender: "@bob:example.com",
|
||||
event_id: "$event",
|
||||
room_id: roomId,
|
||||
content: {
|
||||
algorithm: "m.megolm.v1.aes-sha2",
|
||||
ciphertext: "blablabla",
|
||||
device_id: "bobdevice",
|
||||
sender_key: bobDevice.deviceCurve25519Key,
|
||||
session_id: "session_id",
|
||||
},
|
||||
}))).rejects.toThrow("The sender has blocked you.");
|
||||
});
|
||||
|
||||
it("throws an error describing the lack of an olm session", async function() {
|
||||
const aliceClient = (new TestClient(
|
||||
"@alice:example.com", "alicedevice",
|
||||
)).client;
|
||||
const bobClient = (new TestClient(
|
||||
"@bob:example.com", "bobdevice",
|
||||
)).client;
|
||||
await Promise.all([
|
||||
aliceClient.initCrypto(),
|
||||
bobClient.initCrypto(),
|
||||
]);
|
||||
const bobDevice = bobClient._crypto._olmDevice;
|
||||
|
||||
const roomId = "!someroom";
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
aliceClient._crypto._onToDeviceEvent(new MatrixEvent({
|
||||
type: "org.matrix.room_key.withheld",
|
||||
sender: "@bob:example.com",
|
||||
content: {
|
||||
algorithm: "m.megolm.v1.aes-sha2",
|
||||
room_id: roomId,
|
||||
session_id: "session_id",
|
||||
sender_key: bobDevice.deviceCurve25519Key,
|
||||
code: "m.no_olm",
|
||||
reason: "Unable to establish a secure channel.",
|
||||
},
|
||||
}));
|
||||
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 100);
|
||||
});
|
||||
|
||||
await expect(aliceClient._crypto.decryptEvent(new MatrixEvent({
|
||||
type: "m.room.encrypted",
|
||||
sender: "@bob:example.com",
|
||||
event_id: "$event",
|
||||
room_id: roomId,
|
||||
content: {
|
||||
algorithm: "m.megolm.v1.aes-sha2",
|
||||
ciphertext: "blablabla",
|
||||
device_id: "bobdevice",
|
||||
sender_key: bobDevice.deviceCurve25519Key,
|
||||
session_id: "session_id",
|
||||
},
|
||||
origin_server_ts: now,
|
||||
}))).rejects.toThrow("The sender was unable to establish a secure channel.");
|
||||
});
|
||||
|
||||
it("throws an error to indicate a wedged olm session", async function() {
|
||||
const aliceClient = (new TestClient(
|
||||
"@alice:example.com", "alicedevice",
|
||||
)).client;
|
||||
const bobClient = (new TestClient(
|
||||
"@bob:example.com", "bobdevice",
|
||||
)).client;
|
||||
await Promise.all([
|
||||
aliceClient.initCrypto(),
|
||||
bobClient.initCrypto(),
|
||||
]);
|
||||
const bobDevice = bobClient._crypto._olmDevice;
|
||||
|
||||
const roomId = "!someroom";
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
// pretend we got an event that we can't decrypt
|
||||
aliceClient._crypto._onToDeviceEvent(new MatrixEvent({
|
||||
type: "m.room.encrypted",
|
||||
sender: "@bob:example.com",
|
||||
content: {
|
||||
msgtype: "m.bad.encrypted",
|
||||
algorithm: "m.megolm.v1.aes-sha2",
|
||||
session_id: "session_id",
|
||||
sender_key: bobDevice.deviceCurve25519Key,
|
||||
},
|
||||
}));
|
||||
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 100);
|
||||
});
|
||||
|
||||
await expect(aliceClient._crypto.decryptEvent(new MatrixEvent({
|
||||
type: "m.room.encrypted",
|
||||
sender: "@bob:example.com",
|
||||
event_id: "$event",
|
||||
room_id: roomId,
|
||||
content: {
|
||||
algorithm: "m.megolm.v1.aes-sha2",
|
||||
ciphertext: "blablabla",
|
||||
device_id: "bobdevice",
|
||||
sender_key: bobDevice.deviceCurve25519Key,
|
||||
session_id: "session_id",
|
||||
},
|
||||
origin_server_ts: now,
|
||||
}))).rejects.toThrow("The secure channel with the sender was corrupted.");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright 2018,2019 New Vector Ltd
|
||||
Copyright 2019 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.
|
||||
@@ -15,14 +16,12 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import '../../../olm-loader';
|
||||
|
||||
import MemoryCryptoStore from '../../../../lib/crypto/store/memory-crypto-store.js';
|
||||
import MockStorageApi from '../../../MockStorageApi';
|
||||
import logger from '../../../../src/logger';
|
||||
|
||||
import OlmDevice from '../../../../lib/crypto/OlmDevice';
|
||||
import olmlib from '../../../../lib/crypto/olmlib';
|
||||
import DeviceInfo from '../../../../lib/crypto/deviceinfo';
|
||||
import {MemoryCryptoStore} from "../../../../src/crypto/store/memory-crypto-store";
|
||||
import {MockStorageApi} from "../../../MockStorageApi";
|
||||
import {logger} from "../../../../src/logger";
|
||||
import {OlmDevice} from "../../../../src/crypto/OlmDevice";
|
||||
import * as olmlib from "../../../../src/crypto/olmlib";
|
||||
import {DeviceInfo} from "../../../../src/crypto/deviceinfo";
|
||||
|
||||
function makeOlmDevice() {
|
||||
const mockStorage = new MockStorageApi();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2019 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.
|
||||
@@ -15,25 +16,20 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import '../../olm-loader';
|
||||
|
||||
import Promise from 'bluebird';
|
||||
|
||||
import sdk from '../../..';
|
||||
import algorithms from '../../../lib/crypto/algorithms';
|
||||
import WebStorageSessionStore from '../../../lib/store/session/webstorage';
|
||||
import MemoryCryptoStore from '../../../lib/crypto/store/memory-crypto-store.js';
|
||||
import MockStorageApi from '../../MockStorageApi';
|
||||
import testUtils from '../../test-utils';
|
||||
|
||||
import OlmDevice from '../../../lib/crypto/OlmDevice';
|
||||
import Crypto from '../../../lib/crypto';
|
||||
import logger from '../../../src/logger';
|
||||
import olmlib from '../../../lib/crypto/olmlib';
|
||||
import {logger} from "../../../src/logger";
|
||||
import * as olmlib from "../../../src/crypto/olmlib";
|
||||
import {MatrixClient} from "../../../src/client";
|
||||
import {MatrixEvent} from "../../../src/models/event";
|
||||
import * as algorithms from "../../../src/crypto/algorithms";
|
||||
import {WebStorageSessionStore} from "../../../src/store/session/webstorage";
|
||||
import {MemoryCryptoStore} from "../../../src/crypto/store/memory-crypto-store";
|
||||
import {MockStorageApi} from "../../MockStorageApi";
|
||||
import * as testUtils from "../../test-utils";
|
||||
import {OlmDevice} from "../../../src/crypto/OlmDevice";
|
||||
import {Crypto} from "../../../src/crypto";
|
||||
|
||||
const Olm = global.Olm;
|
||||
|
||||
const MatrixClient = sdk.MatrixClient;
|
||||
const MatrixEvent = sdk.MatrixEvent;
|
||||
const MegolmDecryption = algorithms.DECRYPTION_CLASSES['m.megolm.v1.aes-sha2'];
|
||||
|
||||
const ROOM_ID = '!ROOM:ID';
|
||||
@@ -157,7 +153,7 @@ describe("MegolmBackup", function() {
|
||||
mockOlmLib = {};
|
||||
mockOlmLib.ensureOlmSessionsForDevices = jest.fn();
|
||||
mockOlmLib.encryptMessageForDevice =
|
||||
jest.fn().mockReturnValue(Promise.resolve());
|
||||
jest.fn().mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
describe("backup", function() {
|
||||
@@ -338,7 +334,7 @@ describe("MegolmBackup", function() {
|
||||
});
|
||||
await client.resetCrossSigningKeys();
|
||||
let numCalls = 0;
|
||||
await new Promise(async (resolve, reject) => {
|
||||
await new Promise((resolve, reject) => {
|
||||
client._http.authedRequest = function(
|
||||
callback, method, path, queryParams, data, opts,
|
||||
) {
|
||||
@@ -363,7 +359,7 @@ describe("MegolmBackup", function() {
|
||||
resolve();
|
||||
return Promise.resolve({});
|
||||
};
|
||||
await client.createKeyBackupVersion({
|
||||
client.createKeyBackupVersion({
|
||||
algorithm: "m.megolm_backup.v1",
|
||||
auth_data: {
|
||||
public_key: "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo",
|
||||
|
||||
@@ -16,13 +16,9 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import '../../olm-loader';
|
||||
|
||||
import anotherjson from 'another-json';
|
||||
|
||||
import olmlib from '../../../lib/crypto/olmlib';
|
||||
|
||||
import TestClient from '../../TestClient';
|
||||
|
||||
import * as olmlib from "../../../src/crypto/olmlib";
|
||||
import {TestClient} from '../../TestClient';
|
||||
import {HttpResponse, setHttpResponses} from '../../test-utils';
|
||||
|
||||
async function makeTestClient(userInfo, options, keys) {
|
||||
@@ -789,6 +785,9 @@ describe("Cross Signing", function() {
|
||||
upgradeResolveFunc = resolve;
|
||||
});
|
||||
alice._crypto._deviceList.emit("userCrossSigningUpdated", "@bob:example.com");
|
||||
await new Promise((resolve) => {
|
||||
alice._crypto.on("userTrustStatusChanged", resolve);
|
||||
});
|
||||
await upgradePromise;
|
||||
|
||||
const bobTrust3 = alice.checkUserTrust("@bob:example.com");
|
||||
|
||||
@@ -15,20 +15,23 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import '../../olm-loader';
|
||||
|
||||
import { MatrixEvent } from '../../../lib/models/event';
|
||||
import { SECRET_STORAGE_ALGORITHM_V1 } from '../../../lib/crypto/SecretStorage';
|
||||
|
||||
import olmlib from '../../../lib/crypto/olmlib';
|
||||
|
||||
import TestClient from '../../TestClient';
|
||||
import { makeTestClients } from './verification/util';
|
||||
import * as olmlib from "../../../src/crypto/olmlib";
|
||||
import {SECRET_STORAGE_ALGORITHM_V1} from "../../../src/crypto/SecretStorage";
|
||||
import {MatrixEvent} from "../../../src/models/event";
|
||||
import {TestClient} from '../../TestClient';
|
||||
import {makeTestClients} from './verification/util';
|
||||
|
||||
async function makeTestClient(userInfo, options) {
|
||||
const client = (new TestClient(
|
||||
userInfo.userId, userInfo.deviceId, undefined, undefined, options,
|
||||
)).client;
|
||||
|
||||
// Make it seem as if we've synced and thus the store can be trusted to
|
||||
// contain valid account data.
|
||||
client.isInitialSyncComplete = function() {
|
||||
return true;
|
||||
};
|
||||
|
||||
await client.initCrypto();
|
||||
|
||||
return client;
|
||||
@@ -106,11 +109,11 @@ describe("Secrets", function() {
|
||||
}),
|
||||
]);
|
||||
|
||||
expect(secretStorage.isStored("foo")).toBe(false);
|
||||
expect(await secretStorage.isStored("foo")).toBe(false);
|
||||
|
||||
await secretStorage.store("foo", "bar", ["abc"]);
|
||||
|
||||
expect(secretStorage.isStored("foo")).toBe(true);
|
||||
expect(await secretStorage.isStored("foo")).toBe(true);
|
||||
expect(await secretStorage.get("foo")).toBe("bar");
|
||||
|
||||
expect(getKey).toHaveBeenCalled();
|
||||
@@ -163,7 +166,7 @@ describe("Secrets", function() {
|
||||
};
|
||||
alice.resetCrossSigningKeys();
|
||||
|
||||
const newKeyId = await alice.addSecretKey(
|
||||
const newKeyId = await alice.addSecretStorageKey(
|
||||
SECRET_STORAGE_ALGORITHM_V1,
|
||||
);
|
||||
// we don't await on this because it waits for the event to come down the sync
|
||||
@@ -244,4 +247,90 @@ describe("Secrets", function() {
|
||||
|
||||
expect(secret).toBe("bar");
|
||||
});
|
||||
|
||||
it("bootstraps when no storage or cross-signing keys locally", async function() {
|
||||
const bob = await makeTestClient(
|
||||
{
|
||||
userId: "@bob:example.com",
|
||||
deviceId: "bob1",
|
||||
},
|
||||
);
|
||||
bob.uploadDeviceSigningKeys = async () => {};
|
||||
bob.uploadKeySignatures = async () => {};
|
||||
bob.setAccountData = async function(eventType, contents, callback) {
|
||||
const event = new MatrixEvent({
|
||||
type: eventType,
|
||||
content: contents,
|
||||
});
|
||||
this.store.storeAccountDataEvents([
|
||||
event,
|
||||
]);
|
||||
this.emit("accountData", event);
|
||||
};
|
||||
|
||||
await bob.bootstrapSecretStorage();
|
||||
|
||||
const crossSigning = bob._crypto._crossSigningInfo;
|
||||
const secretStorage = bob._crypto._secretStorage;
|
||||
|
||||
expect(crossSigning.getId()).toBeTruthy();
|
||||
expect(await crossSigning.isStoredInSecretStorage(secretStorage)).toBeTruthy();
|
||||
expect(await secretStorage.hasKey()).toBeTruthy();
|
||||
});
|
||||
|
||||
it("bootstraps when cross-signing keys in secret storage", async function() {
|
||||
const decryption = new global.Olm.PkDecryption();
|
||||
const storagePublicKey = decryption.generate_key();
|
||||
const storagePrivateKey = decryption.get_private_key();
|
||||
|
||||
const bob = await makeTestClient(
|
||||
{
|
||||
userId: "@bob:example.com",
|
||||
deviceId: "bob1",
|
||||
},
|
||||
{
|
||||
cryptoCallbacks: {
|
||||
getSecretStorageKey: async request => {
|
||||
const defaultKeyId = await bob.getDefaultSecretStorageKeyId();
|
||||
expect(Object.keys(request.keys)).toEqual([defaultKeyId]);
|
||||
return [defaultKeyId, storagePrivateKey];
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
bob.uploadDeviceSigningKeys = async () => {};
|
||||
bob.uploadKeySignatures = async () => {};
|
||||
bob.setAccountData = async function(eventType, contents, callback) {
|
||||
const event = new MatrixEvent({
|
||||
type: eventType,
|
||||
content: contents,
|
||||
});
|
||||
this.store.storeAccountDataEvents([
|
||||
event,
|
||||
]);
|
||||
this.emit("accountData", event);
|
||||
};
|
||||
bob._crypto.checkKeyBackup = async () => {};
|
||||
|
||||
const crossSigning = bob._crypto._crossSigningInfo;
|
||||
const secretStorage = bob._crypto._secretStorage;
|
||||
|
||||
// Set up cross-signing keys from scratch with specific storage key
|
||||
await bob.bootstrapSecretStorage({
|
||||
createSecretStorageKey: async () => ({ pubkey: storagePublicKey }),
|
||||
});
|
||||
|
||||
// Clear local cross-signing keys and read from secret storage
|
||||
bob._crypto._deviceList.storeCrossSigningForUser(
|
||||
"@bob:example.com",
|
||||
crossSigning.toStorage(),
|
||||
);
|
||||
crossSigning.keys = {};
|
||||
await bob.bootstrapSecretStorage();
|
||||
|
||||
expect(crossSigning.getId()).toBeTruthy();
|
||||
expect(await crossSigning.isStoredInSecretStorage(secretStorage)).toBeTruthy();
|
||||
expect(await secretStorage.hasKey()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright 2018-2019 New Vector Ltd
|
||||
Copyright 2019 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.
|
||||
@@ -13,17 +14,10 @@ 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.
|
||||
*/
|
||||
import logger from '../../../../src/logger';
|
||||
|
||||
try {
|
||||
global.Olm = require('olm');
|
||||
} catch (e) {
|
||||
logger.warn("unable to run device verification tests: libolm not available");
|
||||
}
|
||||
|
||||
import DeviceInfo from '../../../../lib/crypto/deviceinfo';
|
||||
|
||||
import {ShowQRCode, ScanQRCode} from '../../../../lib/crypto/verification/QRCode';
|
||||
import "../../../olm-loader";
|
||||
import {logger} from "../../../../src/logger";
|
||||
import {DeviceInfo} from "../../../../src/crypto/deviceinfo";
|
||||
import {ScanQRCode, ShowQRCode} from "../../../../src/crypto/verification/QRCode";
|
||||
|
||||
const Olm = global.Olm;
|
||||
|
||||
@@ -39,7 +33,10 @@ describe("QR code verification", function() {
|
||||
|
||||
describe("showing", function() {
|
||||
it("should emit an event to show a QR code", async function() {
|
||||
const qrCode = new ShowQRCode({
|
||||
const channel = {
|
||||
send: jest.fn(),
|
||||
};
|
||||
const qrCode = new ShowQRCode(channel, {
|
||||
getUserId: () => "@alice:example.com",
|
||||
deviceId: "ABCDEFG",
|
||||
getDeviceEd25519Key: function() {
|
||||
@@ -79,7 +76,10 @@ describe("QR code verification", function() {
|
||||
getStoredDevice: jest.fn().mockReturnValue(device),
|
||||
setDeviceVerified: jest.fn(),
|
||||
};
|
||||
const qrCode = new ScanQRCode(client);
|
||||
const channel = {
|
||||
send: jest.fn(),
|
||||
};
|
||||
const qrCode = new ScanQRCode(channel, client);
|
||||
qrCode.on("confirm_user_id", ({userId, confirm}) => {
|
||||
if (userId === "@alice:example.com") {
|
||||
confirm();
|
||||
@@ -102,13 +102,17 @@ describe("QR code verification", function() {
|
||||
getStoredDevice: jest.fn(),
|
||||
setDeviceVerified: jest.fn(),
|
||||
};
|
||||
const qrCode = new ScanQRCode(client, "@bob:example.com", "ABCDEFG");
|
||||
const channel = {
|
||||
send: jest.fn(),
|
||||
};
|
||||
const qrCode = new ScanQRCode(channel, client, "@bob:example.com", "ABCDEFG");
|
||||
qrCode.on("scan", ({done}) => {
|
||||
done(QR_CODE_URL);
|
||||
});
|
||||
const spy = jest.fn();
|
||||
await qrCode.verify().catch(spy);
|
||||
expect(spy).toHaveBeenCalled();
|
||||
expect(channel.send).toHaveBeenCalled();
|
||||
expect(client.getStoredDevice).not.toHaveBeenCalled();
|
||||
expect(client.setDeviceVerified).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -131,13 +135,18 @@ describe("QR code verification", function() {
|
||||
getStoredDevice: jest.fn().mockReturnValue(device),
|
||||
setDeviceVerified: jest.fn(),
|
||||
};
|
||||
const qrCode = new ScanQRCode(client, "@alice:example.com", "ABCDEFG");
|
||||
const channel = {
|
||||
send: jest.fn(),
|
||||
};
|
||||
const qrCode = new ScanQRCode(
|
||||
channel, client, "@alice:example.com", "ABCDEFG");
|
||||
qrCode.on("scan", ({done}) => {
|
||||
done(QR_CODE_URL);
|
||||
});
|
||||
const spy = jest.fn();
|
||||
await qrCode.verify().catch(spy);
|
||||
expect(spy).toHaveBeenCalled();
|
||||
expect(channel.send).toHaveBeenCalled();
|
||||
expect(client.getStoredDevice).toHaveBeenCalled();
|
||||
expect(client.setDeviceVerified).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright 2019 New Vector Ltd
|
||||
Copyright 2019 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.
|
||||
@@ -13,22 +14,14 @@ 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.
|
||||
*/
|
||||
import logger from '../../../../src/logger';
|
||||
|
||||
try {
|
||||
global.Olm = require('olm');
|
||||
} catch (e) {
|
||||
logger.warn("unable to run device verification tests: libolm not available");
|
||||
}
|
||||
|
||||
import {verificationMethods} from '../../../../lib/crypto';
|
||||
|
||||
import SAS from '../../../../lib/crypto/verification/SAS';
|
||||
import "../../../olm-loader";
|
||||
import {verificationMethods} from "../../../../src/crypto";
|
||||
import {logger} from "../../../../src/logger";
|
||||
import {SAS} from "../../../../src/crypto/verification/SAS";
|
||||
import {makeTestClients} from './util';
|
||||
|
||||
const Olm = global.Olm;
|
||||
|
||||
import {makeTestClients} from './util';
|
||||
|
||||
describe("verification request", function() {
|
||||
if (!global.Olm) {
|
||||
logger.warn('Not running device verification unit tests: libolm not present');
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright 2018-2019 New Vector Ltd
|
||||
Copyright 2019 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.
|
||||
@@ -13,29 +14,17 @@ 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.
|
||||
*/
|
||||
import logger from '../../../../src/logger';
|
||||
|
||||
try {
|
||||
global.Olm = require('olm');
|
||||
} catch (e) {
|
||||
logger.warn("unable to run device verification tests: libolm not available");
|
||||
}
|
||||
|
||||
import olmlib from '../../../../lib/crypto/olmlib';
|
||||
|
||||
import sdk from '../../../..';
|
||||
|
||||
import {verificationMethods} from '../../../../lib/crypto';
|
||||
import DeviceInfo from '../../../../lib/crypto/deviceinfo';
|
||||
|
||||
import SAS from '../../../../lib/crypto/verification/SAS';
|
||||
import "../../../olm-loader";
|
||||
import {makeTestClients} from './util';
|
||||
import {MatrixEvent} from "../../../../src/models/event";
|
||||
import {SAS} from "../../../../src/crypto/verification/SAS";
|
||||
import {DeviceInfo} from "../../../../src/crypto/deviceinfo";
|
||||
import {verificationMethods} from "../../../../src/crypto";
|
||||
import * as olmlib from "../../../../src/crypto/olmlib";
|
||||
import {logger} from "../../../../src/logger";
|
||||
|
||||
const Olm = global.Olm;
|
||||
|
||||
const MatrixEvent = sdk.MatrixEvent;
|
||||
|
||||
import {makeTestClients} from './util';
|
||||
|
||||
let ALICE_DEVICES;
|
||||
let BOB_DEVICES;
|
||||
|
||||
@@ -430,32 +419,26 @@ describe("SAS verification", function() {
|
||||
bobSasEvent = null;
|
||||
|
||||
bobPromise = new Promise((resolve, reject) => {
|
||||
bob.client.on("event", async (event) => {
|
||||
const content = event.getContent();
|
||||
if (event.getType() === "m.room.message"
|
||||
&& content.msgtype === "m.key.verification.request") {
|
||||
expect(content.methods).toContain(SAS.NAME);
|
||||
expect(content.to).toBe(bob.client.getUserId());
|
||||
const verifier = bob.client.acceptVerificationDM(event, SAS.NAME);
|
||||
verifier.on("show_sas", (e) => {
|
||||
if (!e.sas.emoji || !e.sas.decimal) {
|
||||
e.cancel();
|
||||
} else if (!aliceSasEvent) {
|
||||
bobSasEvent = e;
|
||||
} else {
|
||||
try {
|
||||
expect(e.sas).toEqual(aliceSasEvent.sas);
|
||||
e.confirm();
|
||||
aliceSasEvent.confirm();
|
||||
} catch (error) {
|
||||
e.mismatch();
|
||||
aliceSasEvent.mismatch();
|
||||
}
|
||||
bob.client.on("crypto.verification.request", async (request) => {
|
||||
const verifier = request.beginKeyVerification(SAS.NAME);
|
||||
verifier.on("show_sas", (e) => {
|
||||
if (!e.sas.emoji || !e.sas.decimal) {
|
||||
e.cancel();
|
||||
} else if (!aliceSasEvent) {
|
||||
bobSasEvent = e;
|
||||
} else {
|
||||
try {
|
||||
expect(e.sas).toEqual(aliceSasEvent.sas);
|
||||
e.confirm();
|
||||
aliceSasEvent.confirm();
|
||||
} catch (error) {
|
||||
e.mismatch();
|
||||
aliceSasEvent.mismatch();
|
||||
}
|
||||
});
|
||||
await verifier.verify();
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
});
|
||||
await verifier.verify();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -479,6 +462,12 @@ describe("SAS verification", function() {
|
||||
}
|
||||
});
|
||||
});
|
||||
afterEach(async function() {
|
||||
await Promise.all([
|
||||
alice.stop(),
|
||||
bob.stop(),
|
||||
]);
|
||||
});
|
||||
|
||||
it("should verify a key", async function() {
|
||||
await Promise.all([
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright 2019 New Vector Ltd
|
||||
Copyright 2019 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.
|
||||
@@ -14,10 +15,8 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import TestClient from '../../../TestClient';
|
||||
|
||||
import sdk from '../../../..';
|
||||
const MatrixEvent = sdk.MatrixEvent;
|
||||
import {TestClient} from '../../../TestClient';
|
||||
import {MatrixEvent} from "../../../../src/models/event";
|
||||
|
||||
export async function makeTestClients(userInfos, options) {
|
||||
const clients = [];
|
||||
@@ -60,7 +59,7 @@ export async function makeTestClients(userInfos, options) {
|
||||
});
|
||||
for (const tc of clients) {
|
||||
setTimeout(
|
||||
() => tc.client.emit("event", event),
|
||||
() => tc.client.emit("Room.timeline", event),
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
"use strict";
|
||||
import 'source-map-support/register';
|
||||
const sdk = require("../..");
|
||||
const EventTimeline = sdk.EventTimeline;
|
||||
const utils = require("../test-utils");
|
||||
import * as utils from "../test-utils";
|
||||
import {EventTimeline} from "../../src/models/event-timeline";
|
||||
import {RoomState} from "../../src/models/room-state";
|
||||
|
||||
function mockRoomStates(timeline) {
|
||||
timeline._startState = utils.mock(sdk.RoomState, "startState");
|
||||
timeline._endState = utils.mock(sdk.RoomState, "endState");
|
||||
timeline._startState = utils.mock(RoomState, "startState");
|
||||
timeline._endState = utils.mock(RoomState, "endState");
|
||||
}
|
||||
|
||||
describe("EventTimeline", function() {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright 2017 New Vector Ltd
|
||||
Copyright 2019 The Matrix.org Foundaction 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.
|
||||
@@ -14,11 +15,8 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import sdk from '../..';
|
||||
const MatrixEvent = sdk.MatrixEvent;
|
||||
|
||||
import Promise from 'bluebird';
|
||||
import logger from '../../src/logger';
|
||||
import {logger} from "../../src/logger";
|
||||
import {MatrixEvent} from "../../src/models/event";
|
||||
|
||||
describe("MatrixEvent", () => {
|
||||
describe(".attemptDecryption", () => {
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
"use strict";
|
||||
import 'source-map-support/register';
|
||||
const sdk = require("../..");
|
||||
const Filter = sdk.Filter;
|
||||
import {Filter} from "../../src/filter";
|
||||
|
||||
describe("Filter", function() {
|
||||
const filterId = "f1lt3ring15g00d4ursoul";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
Copyright 2019 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.
|
||||
@@ -13,16 +14,10 @@ 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.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
import 'source-map-support/register';
|
||||
import Promise from 'bluebird';
|
||||
const sdk = require("../..");
|
||||
|
||||
const InteractiveAuth = sdk.InteractiveAuth;
|
||||
const MatrixError = sdk.MatrixError;
|
||||
|
||||
import logger from '../../src/logger';
|
||||
import {logger} from "../../src/logger";
|
||||
import {InteractiveAuth} from "../../src/interactive-auth";
|
||||
import {MatrixError} from "../../src/http-api";
|
||||
|
||||
// Trivial client object to test interactive auth
|
||||
// (we do not need TestClient here)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import TestClient from '../TestClient';
|
||||
import {TestClient} from '../TestClient';
|
||||
|
||||
describe('Login request', function() {
|
||||
let client;
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
"use strict";
|
||||
import 'source-map-support/register';
|
||||
import Promise from 'bluebird';
|
||||
const sdk = require("../..");
|
||||
const MatrixClient = sdk.MatrixClient;
|
||||
|
||||
import logger from '../../src/logger';
|
||||
import {logger} from "../../src/logger";
|
||||
import {MatrixClient} from "../../src/client";
|
||||
import {Filter} from "../../src/filter";
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
@@ -83,7 +79,7 @@ describe("MatrixClient", function() {
|
||||
);
|
||||
}
|
||||
pendingLookup = {
|
||||
promise: Promise.defer().promise,
|
||||
promise: new Promise(() => {}),
|
||||
method: method,
|
||||
path: path,
|
||||
};
|
||||
@@ -119,7 +115,7 @@ describe("MatrixClient", function() {
|
||||
return Promise.resolve(next.data);
|
||||
}
|
||||
expect(true).toBe(false, "Expected different request. " + logLine);
|
||||
return Promise.defer().promise;
|
||||
return new Promise(() => {});
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
@@ -171,7 +167,7 @@ describe("MatrixClient", function() {
|
||||
// a DIFFERENT test (pollution between tests!) - we return unresolved
|
||||
// promises to stop the client from continuing to run.
|
||||
client._http.authedRequest.mockImplementation(function() {
|
||||
return Promise.defer().promise;
|
||||
return new Promise(() => {});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -181,7 +177,7 @@ describe("MatrixClient", function() {
|
||||
httpLookups.push(SYNC_RESPONSE);
|
||||
const filterId = "ehfewf";
|
||||
store.getFilterIdByName.mockReturnValue(filterId);
|
||||
const filter = new sdk.Filter(0, filterId);
|
||||
const filter = new Filter(0, filterId);
|
||||
filter.setDefinition({"room": {"timeline": {"limit": 8}}});
|
||||
store.getFilter.mockReturnValue(filter);
|
||||
const syncPromise = new Promise((resolve, reject) => {
|
||||
@@ -248,7 +244,7 @@ describe("MatrixClient", function() {
|
||||
|
||||
const filterName = getFilterName(client.credentials.userId);
|
||||
client.store.setFilterIdByName(filterName, invalidFilterId);
|
||||
const filter = new sdk.Filter(client.credentials.userId);
|
||||
const filter = new Filter(client.credentials.userId);
|
||||
|
||||
client.getOrCreateFilter(filterName, filter).then(function(filterId) {
|
||||
expect(filterId).toEqual(FILTER_RESPONSE.data.filter_id);
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"use strict";
|
||||
import 'source-map-support/register';
|
||||
const PushProcessor = require("../../lib/pushprocessor");
|
||||
const utils = require("../test-utils");
|
||||
import * as utils from "../test-utils";
|
||||
import {PushProcessor} from "../../src/pushprocessor";
|
||||
|
||||
describe('NotificationService', function() {
|
||||
const testUserId = "@ali:matrix.org";
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
"use strict";
|
||||
|
||||
import 'source-map-support/register';
|
||||
const callbacks = require("../../src/realtime-callbacks");
|
||||
import * as callbacks from "../../src/realtime-callbacks";
|
||||
|
||||
let wallTime = 1234567890;
|
||||
jest.useFakeTimers();
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
"use strict";
|
||||
import 'source-map-support/register';
|
||||
const sdk = require("../..");
|
||||
const RoomMember = sdk.RoomMember;
|
||||
const utils = require("../test-utils");
|
||||
import * as utils from "../test-utils";
|
||||
import {RoomMember} from "../../src/models/room-member";
|
||||
|
||||
describe("RoomMember", function() {
|
||||
const roomId = "!foo:bar";
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
"use strict";
|
||||
import 'source-map-support/register';
|
||||
const sdk = require("../..");
|
||||
const RoomState = sdk.RoomState;
|
||||
const RoomMember = sdk.RoomMember;
|
||||
const utils = require("../test-utils");
|
||||
import * as utils from "../test-utils";
|
||||
import {RoomState} from "../../src/models/room-state";
|
||||
import {RoomMember} from "../../src/models/room-member";
|
||||
|
||||
describe("RoomState", function() {
|
||||
const roomId = "!foo:bar";
|
||||
|
||||
+7
-11
@@ -1,12 +1,8 @@
|
||||
"use strict";
|
||||
import 'source-map-support/register';
|
||||
const sdk = require("../..");
|
||||
const Room = sdk.Room;
|
||||
const RoomState = sdk.RoomState;
|
||||
const MatrixEvent = sdk.MatrixEvent;
|
||||
const EventStatus = sdk.EventStatus;
|
||||
const EventTimeline = sdk.EventTimeline;
|
||||
const utils = require("../test-utils");
|
||||
import * as utils from "../test-utils";
|
||||
import {EventStatus, MatrixEvent} from "../../src/models/event";
|
||||
import {EventTimeline} from "../../src/models/event-timeline";
|
||||
import {RoomState} from "../../src/models/room-state";
|
||||
import {Room} from "../../src/models/room";
|
||||
|
||||
describe("Room", function() {
|
||||
const roomId = "!foo:bar";
|
||||
@@ -20,9 +16,9 @@ describe("Room", function() {
|
||||
room = new Room(roomId);
|
||||
// mock RoomStates
|
||||
room.oldState = room.getLiveTimeline()._startState =
|
||||
utils.mock(sdk.RoomState, "oldState");
|
||||
utils.mock(RoomState, "oldState");
|
||||
room.currentState = room.getLiveTimeline()._endState =
|
||||
utils.mock(sdk.RoomState, "currentState");
|
||||
utils.mock(RoomState, "currentState");
|
||||
});
|
||||
|
||||
describe("getAvatarUrl", function() {
|
||||
|
||||
+18
-20
@@ -1,12 +1,10 @@
|
||||
// This file had a function whose name is all caps, which displeases eslint
|
||||
/* eslint new-cap: "off" */
|
||||
|
||||
import 'source-map-support/register';
|
||||
import Promise from 'bluebird';
|
||||
const sdk = require("../..");
|
||||
const MatrixScheduler = sdk.MatrixScheduler;
|
||||
const MatrixError = sdk.MatrixError;
|
||||
const utils = require("../test-utils");
|
||||
import {defer} from '../../src/utils';
|
||||
import {MatrixError} from "../../src/http-api";
|
||||
import {MatrixScheduler} from "../../src/scheduler";
|
||||
import * as utils from "../test-utils";
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
@@ -14,7 +12,7 @@ describe("MatrixScheduler", function() {
|
||||
let scheduler;
|
||||
let retryFn;
|
||||
let queueFn;
|
||||
let defer;
|
||||
let deferred;
|
||||
const roomId = "!foo:bar";
|
||||
const eventA = utils.mkMessage({
|
||||
user: "@alice:bar", room: roomId, event: true,
|
||||
@@ -37,7 +35,7 @@ describe("MatrixScheduler", function() {
|
||||
});
|
||||
retryFn = null;
|
||||
queueFn = null;
|
||||
defer = Promise.defer();
|
||||
deferred = defer();
|
||||
});
|
||||
|
||||
it("should process events in a queue in a FIFO manner", async function() {
|
||||
@@ -47,8 +45,8 @@ describe("MatrixScheduler", function() {
|
||||
queueFn = function() {
|
||||
return "one_big_queue";
|
||||
};
|
||||
const deferA = Promise.defer();
|
||||
const deferB = Promise.defer();
|
||||
const deferA = defer();
|
||||
const deferB = defer();
|
||||
let yieldedA = false;
|
||||
scheduler.setProcessFunction(function(event) {
|
||||
if (yieldedA) {
|
||||
@@ -74,7 +72,7 @@ describe("MatrixScheduler", function() {
|
||||
it("should invoke the retryFn on failure and wait the amount of time specified",
|
||||
async function() {
|
||||
const waitTimeMs = 1500;
|
||||
const retryDefer = Promise.defer();
|
||||
const retryDefer = defer();
|
||||
retryFn = function() {
|
||||
retryDefer.resolve();
|
||||
return waitTimeMs;
|
||||
@@ -88,9 +86,9 @@ describe("MatrixScheduler", function() {
|
||||
procCount += 1;
|
||||
if (procCount === 1) {
|
||||
expect(ev).toEqual(eventA);
|
||||
return defer.promise;
|
||||
return deferred.promise;
|
||||
} else if (procCount === 2) {
|
||||
// don't care about this defer
|
||||
// don't care about this deferred
|
||||
return new Promise();
|
||||
}
|
||||
expect(procCount).toBeLessThan(3);
|
||||
@@ -101,7 +99,7 @@ describe("MatrixScheduler", function() {
|
||||
// wait just long enough before it does
|
||||
await Promise.resolve();
|
||||
expect(procCount).toEqual(1);
|
||||
defer.reject({});
|
||||
deferred.reject({});
|
||||
await retryDefer.promise;
|
||||
expect(procCount).toEqual(1);
|
||||
jest.advanceTimersByTime(waitTimeMs);
|
||||
@@ -121,8 +119,8 @@ describe("MatrixScheduler", function() {
|
||||
return "yep";
|
||||
};
|
||||
|
||||
const deferA = Promise.defer();
|
||||
const deferB = Promise.defer();
|
||||
const deferA = defer();
|
||||
const deferB = defer();
|
||||
let procCount = 0;
|
||||
scheduler.setProcessFunction(function(ev) {
|
||||
procCount += 1;
|
||||
@@ -177,14 +175,14 @@ describe("MatrixScheduler", function() {
|
||||
const expectOrder = [
|
||||
eventA.getId(), eventB.getId(), eventD.getId(),
|
||||
];
|
||||
const deferA = Promise.defer();
|
||||
const deferA = defer();
|
||||
scheduler.setProcessFunction(function(event) {
|
||||
const id = expectOrder.shift();
|
||||
expect(id).toEqual(event.getId());
|
||||
if (expectOrder.length === 0) {
|
||||
done();
|
||||
}
|
||||
return id === eventA.getId() ? deferA.promise : defer.promise;
|
||||
return id === eventA.getId() ? deferA.promise : deferred.promise;
|
||||
});
|
||||
scheduler.queueEvent(eventA);
|
||||
scheduler.queueEvent(eventB);
|
||||
@@ -298,7 +296,7 @@ describe("MatrixScheduler", function() {
|
||||
scheduler.setProcessFunction(function(ev) {
|
||||
procCount += 1;
|
||||
expect(ev).toEqual(eventA);
|
||||
return defer.promise;
|
||||
return deferred.promise;
|
||||
});
|
||||
// as queueing doesn't start processing synchronously anymore (see commit bbdb5ac)
|
||||
// wait just long enough before it does
|
||||
@@ -314,7 +312,7 @@ describe("MatrixScheduler", function() {
|
||||
let procCount = 0;
|
||||
scheduler.setProcessFunction(function(ev) {
|
||||
procCount += 1;
|
||||
return defer.promise;
|
||||
return deferred.promise;
|
||||
});
|
||||
expect(procCount).toEqual(0);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2019 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.
|
||||
@@ -14,11 +15,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
import 'source-map-support/register';
|
||||
import sdk from "../..";
|
||||
|
||||
const SyncAccumulator = sdk.SyncAccumulator;
|
||||
import {SyncAccumulator} from "../../src/sync-accumulator";
|
||||
|
||||
describe("SyncAccumulator", function() {
|
||||
let sa;
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
"use strict";
|
||||
import 'source-map-support/register';
|
||||
import Promise from 'bluebird';
|
||||
const sdk = require("../..");
|
||||
const EventTimeline = sdk.EventTimeline;
|
||||
const TimelineWindow = sdk.TimelineWindow;
|
||||
const TimelineIndex = require("../../lib/timeline-window").TimelineIndex;
|
||||
|
||||
const utils = require("../test-utils");
|
||||
import {EventTimeline} from "../../src/models/event-timeline";
|
||||
import {TimelineIndex, TimelineWindow} from "../../src/timeline-window";
|
||||
import * as utils from "../test-utils";
|
||||
|
||||
const ROOM_ID = "roomId";
|
||||
const USER_ID = "userId";
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
"use strict";
|
||||
import 'source-map-support/register';
|
||||
const sdk = require("../..");
|
||||
const User = sdk.User;
|
||||
const utils = require("../test-utils");
|
||||
import {User} from "../../src/models/user";
|
||||
import * as utils from "../test-utils";
|
||||
|
||||
describe("User", function() {
|
||||
const userId = "@alice:bar";
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"use strict";
|
||||
import 'source-map-support/register';
|
||||
const utils = require("../../lib/utils");
|
||||
import * as utils from "../../src/utils";
|
||||
|
||||
describe("utils", function() {
|
||||
describe("encodeParams", function() {
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ limitations under the License.
|
||||
* @module
|
||||
*/
|
||||
|
||||
export default class Reemitter {
|
||||
export class ReEmitter {
|
||||
constructor(target) {
|
||||
this.target = target;
|
||||
|
||||
|
||||
@@ -17,9 +17,8 @@ limitations under the License.
|
||||
|
||||
/** @module auto-discovery */
|
||||
|
||||
import Promise from 'bluebird';
|
||||
import logger from './logger';
|
||||
import { URL as NodeURL } from "url";
|
||||
import {logger} from './logger';
|
||||
import {URL as NodeURL} from "url";
|
||||
|
||||
// Dev note: Auto discovery is part of the spec.
|
||||
// See: https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
|
||||
|
||||
+35
-35
@@ -16,7 +16,6 @@ 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.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This is an internal module. MatrixBaseApis is currently only meant to be used
|
||||
@@ -25,17 +24,22 @@ limitations under the License.
|
||||
* @module base-apis
|
||||
*/
|
||||
|
||||
import { SERVICE_TYPES } from './service-types';
|
||||
import logger from './logger';
|
||||
|
||||
const httpApi = require("./http-api");
|
||||
const utils = require("./utils");
|
||||
const PushProcessor = require("./pushprocessor");
|
||||
import {SERVICE_TYPES} from './service-types';
|
||||
import {logger} from './logger';
|
||||
import {PushProcessor} from "./pushprocessor";
|
||||
import * as utils from "./utils";
|
||||
import {
|
||||
MatrixHttpApi,
|
||||
PREFIX_IDENTITY_V1,
|
||||
PREFIX_IDENTITY_V2,
|
||||
PREFIX_R0,
|
||||
PREFIX_UNSTABLE,
|
||||
} from "./http-api";
|
||||
|
||||
function termsUrlForService(serviceType, baseUrl) {
|
||||
switch (serviceType) {
|
||||
case SERVICE_TYPES.IS:
|
||||
return baseUrl + httpApi.PREFIX_IDENTITY_V2 + '/terms';
|
||||
return baseUrl + PREFIX_IDENTITY_V2 + '/terms';
|
||||
case SERVICE_TYPES.IM:
|
||||
return baseUrl + '/_matrix/integrations/v1/terms';
|
||||
default:
|
||||
@@ -83,7 +87,7 @@ function termsUrlForService(serviceType, baseUrl) {
|
||||
* @param {boolean} [opts.useAuthorizationHeader = false] Set to true to use
|
||||
* Authorization header instead of query param to send the access token to the server.
|
||||
*/
|
||||
function MatrixBaseApis(opts) {
|
||||
export function MatrixBaseApis(opts) {
|
||||
utils.checkObjectHasKeys(opts, ["baseUrl", "request"]);
|
||||
|
||||
this.baseUrl = opts.baseUrl;
|
||||
@@ -95,13 +99,13 @@ function MatrixBaseApis(opts) {
|
||||
idBaseUrl: opts.idBaseUrl,
|
||||
accessToken: opts.accessToken,
|
||||
request: opts.request,
|
||||
prefix: httpApi.PREFIX_R0,
|
||||
prefix: PREFIX_R0,
|
||||
onlyData: true,
|
||||
extraParams: opts.queryParams,
|
||||
localTimeoutMs: opts.localTimeoutMs,
|
||||
useAuthorizationHeader: opts.useAuthorizationHeader,
|
||||
};
|
||||
this._http = new httpApi.MatrixHttpApi(this, httpOpts);
|
||||
this._http = new MatrixHttpApi(this, httpOpts);
|
||||
|
||||
this._txnCtr = 0;
|
||||
}
|
||||
@@ -369,7 +373,7 @@ MatrixBaseApis.prototype.getSsoLoginUrl = function(redirectUrl, loginType) {
|
||||
}
|
||||
return this._http.getUrl("/login/"+loginType+"/redirect", {
|
||||
"redirectUrl": redirectUrl,
|
||||
}, httpApi.PREFIX_R0);
|
||||
}, PREFIX_R0);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -447,7 +451,7 @@ MatrixBaseApis.prototype.getFallbackAuthUrl = function(loginType, authSessionId)
|
||||
|
||||
return this._http.getUrl(path, {
|
||||
session: authSessionId,
|
||||
}, httpApi.PREFIX_R0);
|
||||
}, PREFIX_R0);
|
||||
};
|
||||
|
||||
// Room operations
|
||||
@@ -499,7 +503,7 @@ MatrixBaseApis.prototype.fetchRelations =
|
||||
});
|
||||
const response = await this._http.authedRequest(
|
||||
undefined, "GET", path, null, null, {
|
||||
prefix: httpApi.PREFIX_UNSTABLE,
|
||||
prefix: PREFIX_UNSTABLE,
|
||||
},
|
||||
);
|
||||
return response;
|
||||
@@ -1377,7 +1381,7 @@ MatrixBaseApis.prototype.addThreePid = function(creds, bind, callback) {
|
||||
MatrixBaseApis.prototype.addThreePidOnly = async function(data) {
|
||||
const path = "/account/3pid/add";
|
||||
const prefix = await this.isVersionSupported("r0.6.0") ?
|
||||
httpApi.PREFIX_R0 : httpApi.PREFIX_UNSTABLE;
|
||||
PREFIX_R0 : PREFIX_UNSTABLE;
|
||||
return this._http.authedRequest(
|
||||
undefined, "POST", path, null, data, { prefix },
|
||||
);
|
||||
@@ -1400,7 +1404,7 @@ MatrixBaseApis.prototype.addThreePidOnly = async function(data) {
|
||||
MatrixBaseApis.prototype.bindThreePid = async function(data) {
|
||||
const path = "/account/3pid/bind";
|
||||
const prefix = await this.isVersionSupported("r0.6.0") ?
|
||||
httpApi.PREFIX_R0 : httpApi.PREFIX_UNSTABLE;
|
||||
PREFIX_R0 : PREFIX_UNSTABLE;
|
||||
return this._http.authedRequest(
|
||||
undefined, "POST", path, null, data, { prefix },
|
||||
);
|
||||
@@ -1425,7 +1429,7 @@ MatrixBaseApis.prototype.unbindThreePid = async function(medium, address) {
|
||||
id_server: this.getIdentityServerUrl(true),
|
||||
};
|
||||
const prefix = await this.isVersionSupported("r0.6.0") ?
|
||||
httpApi.PREFIX_R0 : httpApi.PREFIX_UNSTABLE;
|
||||
PREFIX_R0 : PREFIX_UNSTABLE;
|
||||
return this._http.authedRequest(
|
||||
undefined, "POST", path, null, data, { prefix },
|
||||
);
|
||||
@@ -1722,7 +1726,7 @@ MatrixBaseApis.prototype.uploadKeySignatures = function(content) {
|
||||
return this._http.authedRequest(
|
||||
undefined, "POST", '/keys/signatures/upload', undefined,
|
||||
content, {
|
||||
prefix: httpApi.PREFIX_UNSTABLE,
|
||||
prefix: PREFIX_UNSTABLE,
|
||||
},
|
||||
);
|
||||
};
|
||||
@@ -1815,7 +1819,7 @@ MatrixBaseApis.prototype.uploadDeviceSigningKeys = function(auth, keys) {
|
||||
const data = Object.assign({}, keys, {auth});
|
||||
return this._http.authedRequest(
|
||||
undefined, "POST", "/keys/device_signing/upload", undefined, data, {
|
||||
prefix: httpApi.PREFIX_UNSTABLE,
|
||||
prefix: PREFIX_UNSTABLE,
|
||||
},
|
||||
);
|
||||
};
|
||||
@@ -1841,7 +1845,7 @@ MatrixBaseApis.prototype.registerWithIdentityServer = function(hsOpenIdToken) {
|
||||
throw new Error("No Identity Server base URL set");
|
||||
}
|
||||
|
||||
const uri = this.idBaseUrl + httpApi.PREFIX_IDENTITY_V2 + "/account/register";
|
||||
const uri = this.idBaseUrl + PREFIX_IDENTITY_V2 + "/account/register";
|
||||
return this._http.requestOtherUrl(
|
||||
undefined, "POST", uri,
|
||||
null, hsOpenIdToken,
|
||||
@@ -1890,7 +1894,7 @@ MatrixBaseApis.prototype.requestEmailToken = async function(
|
||||
try {
|
||||
const response = await this._http.idServerRequest(
|
||||
undefined, "POST", "/validate/email/requestToken",
|
||||
params, httpApi.PREFIX_IDENTITY_V2, identityAccessToken,
|
||||
params, PREFIX_IDENTITY_V2, identityAccessToken,
|
||||
);
|
||||
// TODO: Fold callback into above call once v1 path below is removed
|
||||
if (callback) callback(null, response);
|
||||
@@ -1903,7 +1907,7 @@ MatrixBaseApis.prototype.requestEmailToken = async function(
|
||||
logger.warn("IS doesn't support v2, falling back to deprecated v1");
|
||||
return await this._http.idServerRequest(
|
||||
callback, "POST", "/validate/email/requestToken",
|
||||
params, httpApi.PREFIX_IDENTITY_V1,
|
||||
params, PREFIX_IDENTITY_V1,
|
||||
);
|
||||
}
|
||||
if (callback) callback(err);
|
||||
@@ -1958,7 +1962,7 @@ MatrixBaseApis.prototype.requestMsisdnToken = async function(
|
||||
try {
|
||||
const response = await this._http.idServerRequest(
|
||||
undefined, "POST", "/validate/msisdn/requestToken",
|
||||
params, httpApi.PREFIX_IDENTITY_V2, identityAccessToken,
|
||||
params, PREFIX_IDENTITY_V2, identityAccessToken,
|
||||
);
|
||||
// TODO: Fold callback into above call once v1 path below is removed
|
||||
if (callback) callback(null, response);
|
||||
@@ -1971,7 +1975,7 @@ MatrixBaseApis.prototype.requestMsisdnToken = async function(
|
||||
logger.warn("IS doesn't support v2, falling back to deprecated v1");
|
||||
return await this._http.idServerRequest(
|
||||
callback, "POST", "/validate/msisdn/requestToken",
|
||||
params, httpApi.PREFIX_IDENTITY_V1,
|
||||
params, PREFIX_IDENTITY_V1,
|
||||
);
|
||||
}
|
||||
if (callback) callback(err);
|
||||
@@ -2013,7 +2017,7 @@ MatrixBaseApis.prototype.submitMsisdnToken = async function(
|
||||
try {
|
||||
return await this._http.idServerRequest(
|
||||
undefined, "POST", "/validate/msisdn/submitToken",
|
||||
params, httpApi.PREFIX_IDENTITY_V2, identityAccessToken,
|
||||
params, PREFIX_IDENTITY_V2, identityAccessToken,
|
||||
);
|
||||
} catch (err) {
|
||||
if (err.cors === "rejected" || err.httpStatus === 404) {
|
||||
@@ -2023,7 +2027,7 @@ MatrixBaseApis.prototype.submitMsisdnToken = async function(
|
||||
logger.warn("IS doesn't support v2, falling back to deprecated v1");
|
||||
return await this._http.idServerRequest(
|
||||
undefined, "POST", "/validate/msisdn/submitToken",
|
||||
params, httpApi.PREFIX_IDENTITY_V1,
|
||||
params, PREFIX_IDENTITY_V1,
|
||||
);
|
||||
}
|
||||
throw err;
|
||||
@@ -2074,7 +2078,7 @@ MatrixBaseApis.prototype.submitMsisdnTokenOtherUrl = function(
|
||||
MatrixBaseApis.prototype.getIdentityHashDetails = function(identityAccessToken) {
|
||||
return this._http.idServerRequest(
|
||||
undefined, "GET", "/hash_details",
|
||||
null, httpApi.PREFIX_IDENTITY_V2, identityAccessToken,
|
||||
null, PREFIX_IDENTITY_V2, identityAccessToken,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2143,7 +2147,7 @@ MatrixBaseApis.prototype.identityHashedLookup = async function(
|
||||
|
||||
const response = await this._http.idServerRequest(
|
||||
undefined, "POST", "/lookup",
|
||||
params, httpApi.PREFIX_IDENTITY_V2, identityAccessToken,
|
||||
params, PREFIX_IDENTITY_V2, identityAccessToken,
|
||||
);
|
||||
|
||||
if (!response || !response['mappings']) return []; // no results
|
||||
@@ -2223,7 +2227,7 @@ MatrixBaseApis.prototype.lookupThreePid = async function(
|
||||
logger.warn("IS doesn't support v2, falling back to deprecated v1");
|
||||
return await this._http.idServerRequest(
|
||||
callback, "GET", "/lookup",
|
||||
params, httpApi.PREFIX_IDENTITY_V1,
|
||||
params, PREFIX_IDENTITY_V1,
|
||||
);
|
||||
}
|
||||
if (callback) callback(err, undefined);
|
||||
@@ -2281,7 +2285,7 @@ MatrixBaseApis.prototype.bulkLookupThreePids = async function(
|
||||
logger.warn("IS doesn't support v2, falling back to deprecated v1");
|
||||
return await this._http.idServerRequest(
|
||||
undefined, "POST", "/bulk_lookup", params,
|
||||
httpApi.PREFIX_IDENTITY_V1, identityAccessToken,
|
||||
PREFIX_IDENTITY_V1, identityAccessToken,
|
||||
);
|
||||
}
|
||||
throw err;
|
||||
@@ -2304,7 +2308,7 @@ MatrixBaseApis.prototype.getIdentityAccount = function(
|
||||
) {
|
||||
return this._http.idServerRequest(
|
||||
undefined, "GET", "/account",
|
||||
undefined, httpApi.PREFIX_IDENTITY_V2, identityAccessToken,
|
||||
undefined, PREFIX_IDENTITY_V2, identityAccessToken,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2432,7 +2436,3 @@ MatrixBaseApis.prototype.reportEvent = function(roomId, eventId, score, reason)
|
||||
return this._http.authedRequest(undefined, "POST", path, null, {score, reason});
|
||||
};
|
||||
|
||||
/**
|
||||
* MatrixBaseApis object
|
||||
*/
|
||||
module.exports = MatrixBaseApis;
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
Copyright 2019 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.
|
||||
*/
|
||||
|
||||
import * as matrixcs from "./matrix";
|
||||
import request from "browser-request";
|
||||
import queryString from "qs";
|
||||
|
||||
matrixcs.request(function(opts, fn) {
|
||||
// We manually fix the query string for browser-request because
|
||||
// it doesn't correctly handle cases like ?via=one&via=two. Instead
|
||||
// we mimic `request`'s query string interface to make it all work
|
||||
// as expected.
|
||||
// browser-request will happily take the constructed string as the
|
||||
// query string without trying to modify it further.
|
||||
opts.qs = queryString.stringify(opts.qs || {}, opts.qsStringifyOptions);
|
||||
return request(opts, fn);
|
||||
});
|
||||
|
||||
// just *accessing* indexedDB throws an exception in firefox with
|
||||
// indexeddb disabled.
|
||||
let indexedDB;
|
||||
try {
|
||||
indexedDB = global.indexedDB;
|
||||
} catch(e) {}
|
||||
|
||||
// if our browser (appears to) support indexeddb, use an indexeddb crypto store.
|
||||
if (indexedDB) {
|
||||
matrixcs.setCryptoStoreFactory(
|
||||
function() {
|
||||
return new matrixcs.IndexedDBCryptoStore(
|
||||
indexedDB, "matrix-js-sdk:crypto",
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// We export 3 things to make browserify happy as well as downstream projects.
|
||||
// It's awkward, but required.
|
||||
export * from "./matrix";
|
||||
export default matrixcs; // keep export for browserify package deps
|
||||
global.matrixcs = matrixcs;
|
||||
+306
-121
@@ -16,52 +16,41 @@ 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.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
const PushProcessor = require('./pushprocessor');
|
||||
import {sleep} from './utils';
|
||||
|
||||
/**
|
||||
* This is an internal module. See {@link MatrixClient} for the public class.
|
||||
* @module client
|
||||
*/
|
||||
const EventEmitter = require("events").EventEmitter;
|
||||
import Promise from 'bluebird';
|
||||
const url = require('url');
|
||||
|
||||
const httpApi = require("./http-api");
|
||||
const MatrixEvent = require("./models/event").MatrixEvent;
|
||||
const EventStatus = require("./models/event").EventStatus;
|
||||
const EventTimeline = require("./models/event-timeline");
|
||||
const SearchResult = require("./models/search-result");
|
||||
const StubStore = require("./store/stub");
|
||||
const webRtcCall = require("./webrtc/call");
|
||||
const utils = require("./utils");
|
||||
const contentRepo = require("./content-repo");
|
||||
const Filter = require("./filter");
|
||||
const SyncApi = require("./sync");
|
||||
const MatrixBaseApis = require("./base-apis");
|
||||
const MatrixError = httpApi.MatrixError;
|
||||
const ContentHelpers = require("./content-helpers");
|
||||
const olmlib = require("./crypto/olmlib");
|
||||
|
||||
import ReEmitter from './ReEmitter';
|
||||
import RoomList from './crypto/RoomList';
|
||||
import logger from './logger';
|
||||
|
||||
import Crypto from './crypto';
|
||||
import { isCryptoAvailable } from './crypto';
|
||||
import { encodeRecoveryKey, decodeRecoveryKey } from './crypto/recoverykey';
|
||||
import { keyFromPassphrase, keyFromAuthData } from './crypto/key_passphrase';
|
||||
import { randomString } from './randomstring';
|
||||
|
||||
// Disable warnings for now: we use deprecated bluebird functions
|
||||
// and need to migrate, but they spam the console with warnings.
|
||||
Promise.config({warnings: false});
|
||||
|
||||
import url from "url";
|
||||
import {EventEmitter} from "events";
|
||||
import {MatrixBaseApis} from "./base-apis";
|
||||
import {Filter} from "./filter";
|
||||
import {SyncApi} from "./sync";
|
||||
import {EventStatus, MatrixEvent} from "./models/event";
|
||||
import {EventTimeline} from "./models/event-timeline";
|
||||
import {SearchResult} from "./models/search-result";
|
||||
import {StubStore} from "./store/stub";
|
||||
import {createNewMatrixCall} from "./webrtc/call";
|
||||
import * as utils from './utils';
|
||||
import {sleep} from './utils';
|
||||
import {MatrixError, PREFIX_MEDIA_R0, PREFIX_UNSTABLE} from "./http-api";
|
||||
import {getHttpUriForMxc} from "./content-repo";
|
||||
import * as ContentHelpers from "./content-helpers";
|
||||
import * as olmlib from "./crypto/olmlib";
|
||||
import {ReEmitter} from './ReEmitter';
|
||||
import {RoomList} from './crypto/RoomList';
|
||||
import {logger} from './logger';
|
||||
import {Crypto, isCryptoAvailable} from './crypto';
|
||||
import {decodeRecoveryKey} from './crypto/recoverykey';
|
||||
import {keyFromAuthData} from './crypto/key_passphrase';
|
||||
import {randomString} from './randomstring';
|
||||
import {PushProcessor} from "./pushprocessor";
|
||||
import {encodeBase64, decodeBase64} from "./crypto/olmlib";
|
||||
|
||||
const SCROLLBACK_DELAY_MS = 3000;
|
||||
const CRYPTO_ENABLED = isCryptoAvailable();
|
||||
export const CRYPTO_ENABLED = isCryptoAvailable();
|
||||
const CAPABILITIES_CACHE_MS = 21600000; // 6 hours - an arbitrary value
|
||||
|
||||
function keysFromRecoverySession(sessions, decryptionKey, roomId) {
|
||||
@@ -181,7 +170,8 @@ function keyFromRecoverySession(session, decryptionKey) {
|
||||
* The cross-signing API is currently UNSTABLE and may change without notice.
|
||||
*
|
||||
* @param {function} [opts.cryptoCallbacks.getCrossSigningKey]
|
||||
* Optional (required for cross-signing). Function to call when a cross-signing private key is needed.
|
||||
* Optional. Function to call when a cross-signing private key is needed.
|
||||
* Secure Secret Storage will be used by default if this is unset.
|
||||
* Args:
|
||||
* {string} type The type of key needed. Will be one of "master",
|
||||
* "self_signing", or "user_signing"
|
||||
@@ -193,8 +183,8 @@ function keyFromRecoverySession(session, decryptionKey) {
|
||||
* UInt8Array or rejects with an error.
|
||||
*
|
||||
* @param {function} [opts.cryptoCallbacks.saveCrossSigningKeys]
|
||||
* Optional (required for cross-signing). Called when new private keys
|
||||
* for cross-signing need to be saved.
|
||||
* Optional. Called when new private keys for cross-signing need to be saved.
|
||||
* Secure Secret Storage will be used by default if this is unset.
|
||||
* Args:
|
||||
* {object} keys the private keys to save. Map of key name to private key
|
||||
* as a UInt8Array. The getPrivateKey callback above will be called
|
||||
@@ -215,7 +205,7 @@ function keyFromRecoverySession(session, decryptionKey) {
|
||||
* @param {function} [opts.cryptoCallbacks.getSecretStorageKey]
|
||||
* Optional. Function called when an encryption key for secret storage
|
||||
* is required. One or more keys will be described in the keys object.
|
||||
* The callback function should return with an array of:
|
||||
* The callback function should return a promise with an array of:
|
||||
* [<key name>, <UInt8Array private key>] or null if it cannot provide
|
||||
* any of the keys.
|
||||
* Args:
|
||||
@@ -239,7 +229,7 @@ function keyFromRecoverySession(session, decryptionKey) {
|
||||
* {DeviceTrustLevel} device_trust: The trust status of the device requesting
|
||||
* the secret as returned by {@link module:client~MatrixClient#checkDeviceTrust}.
|
||||
*/
|
||||
function MatrixClient(opts) {
|
||||
export function MatrixClient(opts) {
|
||||
opts.baseUrl = utils.ensureNoTrailingSlash(opts.baseUrl);
|
||||
opts.idBaseUrl = utils.ensureNoTrailingSlash(opts.idBaseUrl);
|
||||
|
||||
@@ -278,7 +268,7 @@ function MatrixClient(opts) {
|
||||
|
||||
// try constructing a MatrixCall to see if we are running in an environment
|
||||
// which has WebRTC. If we are, listen for and handle m.call.* events.
|
||||
const call = webRtcCall.createNewMatrixCall(this);
|
||||
const call = createNewMatrixCall(this);
|
||||
this._supportsVoip = false;
|
||||
if (call) {
|
||||
setupCallEventHandler(this);
|
||||
@@ -298,7 +288,7 @@ function MatrixClient(opts) {
|
||||
this._cryptoStore = opts.cryptoStore;
|
||||
this._sessionStore = opts.sessionStore;
|
||||
this._verificationMethods = opts.verificationMethods;
|
||||
this._cryptoCallbacks = opts.cryptoCallbacks;
|
||||
this._cryptoCallbacks = opts.cryptoCallbacks || {};
|
||||
|
||||
this._forceTURN = opts.forceTURN || false;
|
||||
this._fallbackICEServerAllowed = opts.fallbackICEServerAllowed || false;
|
||||
@@ -508,6 +498,18 @@ MatrixClient.prototype.getSyncStateData = function() {
|
||||
return this._syncApi.getSyncStateData();
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether the initial sync has completed.
|
||||
* @return {boolean} True if at least on sync has happened.
|
||||
*/
|
||||
MatrixClient.prototype.isInitialSyncComplete = function() {
|
||||
const state = this.getSyncState();
|
||||
if (!state) {
|
||||
return false;
|
||||
}
|
||||
return state === "PREPAED" || state === "SYNCING";
|
||||
};
|
||||
|
||||
/**
|
||||
* Return whether the client is configured for a guest account.
|
||||
* @return {boolean} True if this is a guest access_token (or no token is supplied).
|
||||
@@ -677,7 +679,7 @@ MatrixClient.prototype.initCrypto = async function() {
|
||||
"crypto.warning",
|
||||
"crypto.devicesUpdated",
|
||||
"deviceVerificationChanged",
|
||||
"userVerificationChanged",
|
||||
"userTrustStatusChanged",
|
||||
"crossSigning.keysChanged",
|
||||
]);
|
||||
|
||||
@@ -777,7 +779,8 @@ MatrixClient.prototype.getStoredDevice = async function(userId, deviceId) {
|
||||
* Mark the given device as verified
|
||||
*
|
||||
* @param {string} userId owner of the device
|
||||
* @param {string} deviceId unique identifier for the device
|
||||
* @param {string} deviceId unique identifier for the device or user's
|
||||
* cross-signing public key ID.
|
||||
*
|
||||
* @param {boolean=} verified whether to mark the device as verified. defaults
|
||||
* to 'true'.
|
||||
@@ -805,7 +808,8 @@ MatrixClient.prototype.setDeviceVerified = function(userId, deviceId, verified)
|
||||
* Mark the given device as blocked/unblocked
|
||||
*
|
||||
* @param {string} userId owner of the device
|
||||
* @param {string} deviceId unique identifier for the device
|
||||
* @param {string} deviceId unique identifier for the device or user's
|
||||
* cross-signing public key ID.
|
||||
*
|
||||
* @param {boolean=} blocked whether to mark the device as blocked. defaults
|
||||
* to 'true'.
|
||||
@@ -825,7 +829,8 @@ MatrixClient.prototype.setDeviceBlocked = function(userId, deviceId, blocked) {
|
||||
* Mark the given device as known/unknown
|
||||
*
|
||||
* @param {string} userId owner of the device
|
||||
* @param {string} deviceId unique identifier for the device
|
||||
* @param {string} deviceId unique identifier for the device or user's
|
||||
* cross-signing public key ID.
|
||||
*
|
||||
* @param {boolean=} known whether to mark the device as known. defaults
|
||||
* to 'true'.
|
||||
@@ -947,6 +952,35 @@ MatrixClient.prototype.getGlobalBlacklistUnverifiedDevices = function() {
|
||||
return this._crypto.getGlobalBlacklistUnverifiedDevices();
|
||||
};
|
||||
|
||||
/**
|
||||
* Set whether sendMessage in a room with unknown and unverified devices
|
||||
* should throw an error and not send them message. This has 'Global' for
|
||||
* symmetry with setGlobalBlacklistUnverifiedDevices but there is currently
|
||||
* no room-level equivalent for this setting.
|
||||
*
|
||||
* This API is currently UNSTABLE and may change or be removed without notice.
|
||||
*
|
||||
* @param {boolean} value whether error on unknown devices
|
||||
*/
|
||||
MatrixClient.prototype.setGlobalErrorOnUnknownDevices = function(value) {
|
||||
if (this._crypto === null) {
|
||||
throw new Error("End-to-end encryption disabled");
|
||||
}
|
||||
this._crypto.setGlobalErrorOnUnknownDevices(value);
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {boolean} whether to error on unknown devices
|
||||
*
|
||||
* This API is currently UNSTABLE and may change or be removed without notice.
|
||||
*/
|
||||
MatrixClient.prototype.getGlobalErrorOnUnknownDevices = function() {
|
||||
if (this._crypto === null) {
|
||||
throw new Error("End-to-end encryption disabled");
|
||||
}
|
||||
return this._crypto.getGlobalErrorOnUnknownDevices();
|
||||
};
|
||||
|
||||
/**
|
||||
* Add methods that call the corresponding method in this._crypto
|
||||
*
|
||||
@@ -965,14 +999,6 @@ function wrapCryptoFuncs(MatrixClient, names) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether we already have cross-signing keys for the current user.
|
||||
* The cross-signing API is currently UNSTABLE and may change without notice.
|
||||
*
|
||||
* @function module:client~MatrixClient#doesCrossSigningHaveKeys
|
||||
* @return {boolean} Whether we have keys.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generate new cross-signing keys.
|
||||
* The cross-signing API is currently UNSTABLE and may change without notice.
|
||||
@@ -1026,15 +1052,34 @@ function wrapCryptoFuncs(MatrixClient, names) {
|
||||
* @returns {DeviceTrustLevel}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check the copy of our cross-signing key that we have in the device list and
|
||||
* see if we can get the private key. If so, mark it as trusted.
|
||||
* The cross-signing API is currently UNSTABLE and may change without notice.
|
||||
*
|
||||
* @function module:client~MatrixClient#checkOwnCrossSigningTrust
|
||||
*/
|
||||
|
||||
/**
|
||||
* Checks that a given cross-signing private key matches a given public key.
|
||||
* This can be used by the getCrossSigningKey callback to verify that the
|
||||
* private key it is about to supply is the one that was requested.
|
||||
* The cross-signing API is currently UNSTABLE and may change without notice.
|
||||
*
|
||||
* @function module:client~MatrixClient#checkCrossSigningPrivateKey
|
||||
* @param {Uint8Array} privateKey The private key
|
||||
* @param {string} expectedPublicKey The public key
|
||||
* @returns {boolean} true if the key matches, otherwise false
|
||||
*/
|
||||
|
||||
wrapCryptoFuncs(MatrixClient, [
|
||||
"doesCrossSigningHaveKeys",
|
||||
"resetCrossSigningKeys",
|
||||
"getCrossSigningId",
|
||||
"getStoredCrossSigningForUser",
|
||||
"checkUserTrust",
|
||||
"checkDeviceTrust",
|
||||
"checkOwnCrossSigningTrust",
|
||||
"checkPrivateKey",
|
||||
"checkCrossSigningPrivateKey",
|
||||
]);
|
||||
|
||||
/**
|
||||
@@ -1053,21 +1098,56 @@ MatrixClient.prototype.checkEventSenderTrust = async function(event) {
|
||||
return await this._crypto.checkDeviceTrust(event.getSender(), device.deviceId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a recovery key from a user-supplied passphrase.
|
||||
* The Secure Secret Storage API is currently UNSTABLE and may change without notice.
|
||||
*
|
||||
* @function module:client~MatrixClient#createRecoveryKeyFromPassphrase
|
||||
* @param {string} password Passphrase string that can be entered by the user
|
||||
* when restoring the backup as an alternative to entering the recovery key.
|
||||
* Optional.
|
||||
* @returns {Promise<String>} The user-facing recovery key string.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Bootstrap Secure Secret Storage if needed by creating a default key and signing it with
|
||||
* the cross-signing master key. If everything is already set up, then no
|
||||
* changes are made, so this is safe to run to ensure secret storage is ready
|
||||
* for use.
|
||||
* The Secure Secret Storage API is currently UNSTABLE and may change without notice.
|
||||
*
|
||||
* @function module:client~MatrixClient#bootstrapSecretStorage
|
||||
* @param {function} [opts.authUploadDeviceSigningKeys] Optional. Function
|
||||
* called to await an interactive auth flow when uploading device signing keys.
|
||||
* Args:
|
||||
* {function} A function that makes the request requiring auth. Receives the
|
||||
* auth data as an object.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Add a key for encrypting secrets.
|
||||
* The Secure Secret Storage API is currently UNSTABLE and may change without notice.
|
||||
*
|
||||
* @function module:client~MatrixClient#addSecretKey
|
||||
* @function module:client~MatrixClient#addSecretStorageKey
|
||||
* @param {string} algorithm the algorithm used by the key
|
||||
* @param {object} opts the options for the algorithm. The properties used
|
||||
* depend on the algorithm given. This object may be modified to pass
|
||||
* information back about the key.
|
||||
* depend on the algorithm given.
|
||||
* @param {string} [keyName] the name of the key. If not given, a random
|
||||
* name will be generated.
|
||||
*
|
||||
* @return {string} the name of the key
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check whether we have a key with a given ID.
|
||||
* The Secure Secret Storage API is currently UNSTABLE and may change without notice.
|
||||
*
|
||||
* @function module:client~MatrixClient#hasSecretStorageKey
|
||||
* @param {string} [keyId = default key's ID] The ID of the key to check
|
||||
* for. Defaults to the default key ID if not provided.
|
||||
* @return {boolean} Whether we have the key.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Store an encrypted secret on the server
|
||||
* The Secure Secret Storage API is currently UNSTABLE and may change without notice.
|
||||
@@ -1129,14 +1209,30 @@ MatrixClient.prototype.checkEventSenderTrust = async function(event) {
|
||||
* @param {string} keyId The new default key ID
|
||||
*/
|
||||
|
||||
/**
|
||||
* Checks that a given secret storage private key matches a given public key.
|
||||
* This can be used by the getSecretStorageKey callback to verify that the
|
||||
* private key it is about to supply is the one that was requested.
|
||||
* The Secure Secret Storage API is currently UNSTABLE and may change without notice.
|
||||
*
|
||||
* @function module:client~MatrixClient#checkSecretStoragePrivateKey
|
||||
* @param {Uint8Array} privateKey The private key
|
||||
* @param {string} expectedPublicKey The public key
|
||||
* @returns {boolean} true if the key matches, otherwise false
|
||||
*/
|
||||
|
||||
wrapCryptoFuncs(MatrixClient, [
|
||||
"addSecretKey",
|
||||
"createRecoveryKeyFromPassphrase",
|
||||
"bootstrapSecretStorage",
|
||||
"addSecretStorageKey",
|
||||
"hasSecretStorageKey",
|
||||
"storeSecret",
|
||||
"getSecret",
|
||||
"isSecretStored",
|
||||
"requestSecret",
|
||||
"getDefaultSecretStorageKeyId",
|
||||
"setDefaultSecretStorageKeyId",
|
||||
"checkSecretStoragePrivateKey",
|
||||
]);
|
||||
|
||||
/**
|
||||
@@ -1285,7 +1381,7 @@ MatrixClient.prototype.checkKeyBackup = function() {
|
||||
MatrixClient.prototype.getKeyBackupVersion = function() {
|
||||
return this._http.authedRequest(
|
||||
undefined, "GET", "/room_keys/version", undefined, undefined,
|
||||
{prefix: httpApi.PREFIX_UNSTABLE},
|
||||
{prefix: PREFIX_UNSTABLE},
|
||||
).then((res) => {
|
||||
if (res.algorithm !== olmlib.MEGOLM_BACKUP_ALGORITHM) {
|
||||
const err = "Unknown backup algorithm: " + res.algorithm;
|
||||
@@ -1376,38 +1472,50 @@ MatrixClient.prototype.disableKeyBackup = function() {
|
||||
* @param {string} password Passphrase string that can be entered by the user
|
||||
* when restoring the backup as an alternative to entering the recovery key.
|
||||
* Optional.
|
||||
* @param {boolean} [opts.secureSecretStorage = false] Whether to use Secure
|
||||
* Secret Storage to store the key encrypting key backups.
|
||||
* Optional, defaults to false.
|
||||
*
|
||||
* @returns {Promise<object>} Object that can be passed to createKeyBackupVersion and
|
||||
* additionally has a 'recovery_key' member with the user-facing recovery key string.
|
||||
*/
|
||||
MatrixClient.prototype.prepareKeyBackupVersion = async function(password) {
|
||||
MatrixClient.prototype.prepareKeyBackupVersion = async function(
|
||||
password,
|
||||
{ secureSecretStorage = false } = {},
|
||||
) {
|
||||
if (this._crypto === null) {
|
||||
throw new Error("End-to-end encryption disabled");
|
||||
}
|
||||
|
||||
const decryption = new global.Olm.PkDecryption();
|
||||
try {
|
||||
let publicKey;
|
||||
const authData = {};
|
||||
if (password) {
|
||||
const keyInfo = await keyFromPassphrase(password);
|
||||
publicKey = decryption.init_with_private_key(keyInfo.key);
|
||||
authData.private_key_salt = keyInfo.salt;
|
||||
authData.private_key_iterations = keyInfo.iterations;
|
||||
} else {
|
||||
publicKey = decryption.generate_key();
|
||||
}
|
||||
const [keyInfo, encodedPrivateKey, privateKey] =
|
||||
await this.createRecoveryKeyFromPassphrase(password);
|
||||
|
||||
authData.public_key = publicKey;
|
||||
|
||||
return {
|
||||
algorithm: olmlib.MEGOLM_BACKUP_ALGORITHM,
|
||||
auth_data: authData,
|
||||
recovery_key: encodeRecoveryKey(decryption.get_private_key()),
|
||||
};
|
||||
} finally {
|
||||
decryption.free();
|
||||
if (secureSecretStorage) {
|
||||
await this.storeSecret("m.megolm_backup.v1", encodeBase64(privateKey));
|
||||
logger.info("Key backup private key stored in secret storage");
|
||||
}
|
||||
|
||||
// Reshape objects into form expected for key backup
|
||||
const authData = {
|
||||
public_key: keyInfo.pubkey,
|
||||
};
|
||||
if (keyInfo.passphrase) {
|
||||
authData.private_key_salt = keyInfo.passphrase.salt;
|
||||
authData.private_key_iterations = keyInfo.passphrase.iterations;
|
||||
}
|
||||
return {
|
||||
algorithm: olmlib.MEGOLM_BACKUP_ALGORITHM,
|
||||
auth_data: authData,
|
||||
recovery_key: encodedPrivateKey,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Check whether the key backup private key is stored in secret storage.
|
||||
* @return {Promise<boolean>} Whether the backup key is stored.
|
||||
*/
|
||||
MatrixClient.prototype.isKeyBackupKeyStored = async function() {
|
||||
return this.isSecretStored("m.megolm_backup.v1", false /* checkKey */);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1427,25 +1535,35 @@ MatrixClient.prototype.createKeyBackupVersion = async function(info) {
|
||||
auth_data: info.auth_data,
|
||||
};
|
||||
|
||||
// Now sign the backup auth data. Do it as this device first because crypto._signObject
|
||||
// is dumb and bluntly replaces the whole signatures block...
|
||||
// this can probably go away very soon in favour of just signing with the SSK.
|
||||
// Sign the backup auth data with the device key for backwards compat with
|
||||
// older devices with cross-signing. This can probably go away very soon in
|
||||
// favour of just signing with the cross-singing master key.
|
||||
await this._crypto._signObject(data.auth_data);
|
||||
|
||||
if (this._crypto._crossSigningInfo.getId()) {
|
||||
// now also sign the auth data with the master key
|
||||
if (
|
||||
this._cryptoCallbacks.getCrossSigningKey &&
|
||||
this._crypto._crossSigningInfo.getId()
|
||||
) {
|
||||
// now also sign the auth data with the cross-signing master key
|
||||
// we check for the callback explicitly here because we still want to be able
|
||||
// to create an un-cross-signed key backup if there is a cross-signing key but
|
||||
// no callback supplied.
|
||||
await this._crypto._crossSigningInfo.signObject(data.auth_data, "master");
|
||||
}
|
||||
|
||||
const res = await this._http.authedRequest(
|
||||
undefined, "POST", "/room_keys/version", undefined, data,
|
||||
{prefix: httpApi.PREFIX_UNSTABLE},
|
||||
{prefix: PREFIX_UNSTABLE},
|
||||
);
|
||||
this.enableKeyBackup({
|
||||
algorithm: info.algorithm,
|
||||
auth_data: info.auth_data,
|
||||
version: res.version,
|
||||
});
|
||||
|
||||
// We could assume everything's okay and enable directly, but this ensures
|
||||
// we run the same signature verification that will be used for future
|
||||
// sessions.
|
||||
await this.checkKeyBackup();
|
||||
if (!this.getKeyBackupEnabled()) {
|
||||
logger.error("Key backup not usable even though we just created it");
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
@@ -1467,7 +1585,7 @@ MatrixClient.prototype.deleteKeyBackupVersion = function(version) {
|
||||
|
||||
return this._http.authedRequest(
|
||||
undefined, "DELETE", path, undefined, undefined,
|
||||
{prefix: httpApi.PREFIX_UNSTABLE},
|
||||
{prefix: PREFIX_UNSTABLE},
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1509,7 +1627,7 @@ MatrixClient.prototype.sendKeyBackup = function(roomId, sessionId, version, data
|
||||
const path = this._makeKeyBackupPath(roomId, sessionId, version);
|
||||
return this._http.authedRequest(
|
||||
undefined, "PUT", path.path, path.queryData, data,
|
||||
{prefix: httpApi.PREFIX_UNSTABLE},
|
||||
{prefix: PREFIX_UNSTABLE},
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1549,6 +1667,18 @@ MatrixClient.prototype.isValidRecoveryKey = function(recoveryKey) {
|
||||
|
||||
MatrixClient.RESTORE_BACKUP_ERROR_BAD_KEY = 'RESTORE_BACKUP_ERROR_BAD_KEY';
|
||||
|
||||
/**
|
||||
* Restore from an existing key backup via a passphrase.
|
||||
*
|
||||
* @param {string} password Passphrase
|
||||
* @param {string} [targetRoomId] Room ID to target a specific room.
|
||||
* Restores all rooms if omitted.
|
||||
* @param {string} [targetSessionId] Session ID to target a specific session.
|
||||
* Restores all sessions if omitted.
|
||||
* @param {object} backupInfo Backup metadata from `checkKeyBackup`
|
||||
* @return {Promise<object>} Status of restoration with `total` and `imported`
|
||||
* key counts.
|
||||
*/
|
||||
MatrixClient.prototype.restoreKeyBackupWithPassword = async function(
|
||||
password, targetRoomId, targetSessionId, backupInfo,
|
||||
) {
|
||||
@@ -1558,6 +1688,39 @@ MatrixClient.prototype.restoreKeyBackupWithPassword = async function(
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Restore from an existing key backup via a private key stored in secret
|
||||
* storage.
|
||||
*
|
||||
* @param {object} backupInfo Backup metadata from `checkKeyBackup`
|
||||
* @param {string} [targetRoomId] Room ID to target a specific room.
|
||||
* Restores all rooms if omitted.
|
||||
* @param {string} [targetSessionId] Session ID to target a specific session.
|
||||
* Restores all sessions if omitted.
|
||||
* @return {Promise<object>} Status of restoration with `total` and `imported`
|
||||
* key counts.
|
||||
*/
|
||||
MatrixClient.prototype.restoreKeyBackupWithSecretStorage = async function(
|
||||
backupInfo, targetRoomId, targetSessionId,
|
||||
) {
|
||||
const privKey = decodeBase64(await this.getSecret("m.megolm_backup.v1"));
|
||||
return this._restoreKeyBackup(
|
||||
privKey, targetRoomId, targetSessionId, backupInfo,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Restore from an existing key backup via an encoded recovery key.
|
||||
*
|
||||
* @param {string} recoveryKey Encoded recovery key
|
||||
* @param {string} [targetRoomId] Room ID to target a specific room.
|
||||
* Restores all rooms if omitted.
|
||||
* @param {string} [targetSessionId] Session ID to target a specific session.
|
||||
* Restores all sessions if omitted.
|
||||
* @param {object} backupInfo Backup metadata from `checkKeyBackup`
|
||||
* @return {Promise<object>} Status of restoration with `total` and `imported`
|
||||
* key counts.
|
||||
*/
|
||||
MatrixClient.prototype.restoreKeyBackupWithRecoveryKey = function(
|
||||
recoveryKey, targetRoomId, targetSessionId, backupInfo,
|
||||
) {
|
||||
@@ -1598,7 +1761,7 @@ MatrixClient.prototype._restoreKeyBackup = function(
|
||||
|
||||
return this._http.authedRequest(
|
||||
undefined, "GET", path.path, path.queryData, undefined,
|
||||
{prefix: httpApi.PREFIX_UNSTABLE},
|
||||
{prefix: PREFIX_UNSTABLE},
|
||||
).then((res) => {
|
||||
if (res.rooms) {
|
||||
for (const [roomId, roomData] of Object.entries(res.rooms)) {
|
||||
@@ -1606,7 +1769,7 @@ MatrixClient.prototype._restoreKeyBackup = function(
|
||||
|
||||
totalKeyCount += Object.keys(roomData.sessions).length;
|
||||
const roomKeys = keysFromRecoverySession(
|
||||
roomData.sessions, decryption, roomId, roomKeys,
|
||||
roomData.sessions, decryption, roomId,
|
||||
);
|
||||
for (const k of roomKeys) {
|
||||
k.room_id = roomId;
|
||||
@@ -1648,7 +1811,7 @@ MatrixClient.prototype.deleteKeysFromBackup = function(roomId, sessionId, versio
|
||||
const path = this._makeKeyBackupPath(roomId, sessionId, version);
|
||||
return this._http.authedRequest(
|
||||
undefined, "DELETE", path.path, path.queryData, undefined,
|
||||
{prefix: httpApi.PREFIX_UNSTABLE},
|
||||
{prefix: PREFIX_UNSTABLE},
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1684,7 +1847,7 @@ MatrixClient.prototype.getGroups = function() {
|
||||
MatrixClient.prototype.getMediaConfig = function(callback) {
|
||||
return this._http.authedRequest(
|
||||
callback, "GET", "/config", undefined, undefined, {
|
||||
prefix: httpApi.PREFIX_MEDIA_R0,
|
||||
prefix: PREFIX_MEDIA_R0,
|
||||
},
|
||||
);
|
||||
};
|
||||
@@ -1791,6 +1954,34 @@ MatrixClient.prototype.getAccountData = function(eventType) {
|
||||
return this.store.getAccountData(eventType);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get account data event of given type for the current user. This variant
|
||||
* gets account data directly from the homeserver if the local store is not
|
||||
* ready, which can be useful very early in startup before the initial sync.
|
||||
* @param {string} eventType The event type
|
||||
* @return {module:client.Promise} Resolves: The contents of the given account
|
||||
* data event.
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
MatrixClient.prototype.getAccountDataFromServer = async function(eventType) {
|
||||
if (this.isInitialSyncComplete()) {
|
||||
const event = this.store.getAccountData(eventType);
|
||||
if (!event) {
|
||||
return null;
|
||||
}
|
||||
// The network version below returns just the content, so this branch
|
||||
// does the same to match.
|
||||
return event.getContent();
|
||||
}
|
||||
const path = utils.encodeUri("/user/$userId/account_data/$type", {
|
||||
$userId: this.credentials.userId,
|
||||
$type: eventType,
|
||||
});
|
||||
return this._http.authedRequest(
|
||||
undefined, "GET", path, undefined,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the users that are ignored by this client
|
||||
* @returns {string[]} The array of users that are ignored (empty if none)
|
||||
@@ -1892,7 +2083,7 @@ MatrixClient.prototype.joinRoom = function(roomIdOrAlias, opts, callback) {
|
||||
// return syncApi.syncRoom(room);
|
||||
}
|
||||
return Promise.resolve(room);
|
||||
}).done(function(room) {
|
||||
}).then(function(room) {
|
||||
_resolve(callback, resolve, room);
|
||||
}, function(err) {
|
||||
_reject(callback, reject, err);
|
||||
@@ -2621,7 +2812,7 @@ MatrixClient.prototype.getUrlPreview = function(url, ts, callback) {
|
||||
url: url,
|
||||
ts: ts,
|
||||
}, undefined, {
|
||||
prefix: httpApi.PREFIX_MEDIA_R0,
|
||||
prefix: PREFIX_MEDIA_R0,
|
||||
},
|
||||
).then(function(response) {
|
||||
// TODO: expire cache occasionally
|
||||
@@ -3071,7 +3262,7 @@ MatrixClient.prototype.setAvatarUrl = function(url, callback) {
|
||||
*/
|
||||
MatrixClient.prototype.mxcUrlToHttp =
|
||||
function(mxcUrl, width, height, resizeMethod, allowDirectLinks) {
|
||||
return contentRepo.getHttpUriForMxc(
|
||||
return getHttpUriForMxc(
|
||||
this.baseUrl, mxcUrl, width, height, resizeMethod, allowDirectLinks,
|
||||
);
|
||||
};
|
||||
@@ -3224,7 +3415,7 @@ MatrixClient.prototype.scrollback = function(room, limit, callback) {
|
||||
room.oldState.paginationToken,
|
||||
limit,
|
||||
'b');
|
||||
}).done(function(res) {
|
||||
}).then(function(res) {
|
||||
const matrixEvents = utils.map(res.chunk, _PojoToMatrixEventMapper(self));
|
||||
if (res.state) {
|
||||
const stateEvents = utils.map(res.state, _PojoToMatrixEventMapper(self));
|
||||
@@ -3872,10 +4063,10 @@ MatrixClient.prototype.setRoomMutePushRule = function(scope, roomId, mute) {
|
||||
// This is a workaround to SYN-590 (Push rule update fails)
|
||||
deferred = utils.defer();
|
||||
this.deletePushRule(scope, "room", roomPushRule.rule_id)
|
||||
.done(function() {
|
||||
.then(function() {
|
||||
self.addPushRule(scope, "room", roomId, {
|
||||
actions: ["dont_notify"],
|
||||
}).done(function() {
|
||||
}).then(function() {
|
||||
deferred.resolve();
|
||||
}, function(err) {
|
||||
deferred.reject(err);
|
||||
@@ -3891,8 +4082,8 @@ MatrixClient.prototype.setRoomMutePushRule = function(scope, roomId, mute) {
|
||||
if (deferred) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Update this.pushRules when the operation completes
|
||||
deferred.done(function() {
|
||||
self.getPushRules().done(function(result) {
|
||||
deferred.then(function() {
|
||||
self.getPushRules().then(function(result) {
|
||||
self.pushRules = result;
|
||||
resolve();
|
||||
}, function(err) {
|
||||
@@ -3901,7 +4092,7 @@ MatrixClient.prototype.setRoomMutePushRule = function(scope, roomId, mute) {
|
||||
}, function(err) {
|
||||
// Update it even if the previous operation fails. This can help the
|
||||
// app to recover when push settings has been modifed from another client
|
||||
self.getPushRules().done(function(result) {
|
||||
self.getPushRules().then(function(result) {
|
||||
self.pushRules = result;
|
||||
reject(err);
|
||||
}, function(err2) {
|
||||
@@ -4395,7 +4586,7 @@ MatrixClient.prototype.startClient = async function(opts) {
|
||||
}
|
||||
|
||||
if (this._crypto) {
|
||||
this._crypto.uploadDeviceKeys().done();
|
||||
this._crypto.uploadDeviceKeys();
|
||||
this._crypto.start();
|
||||
}
|
||||
|
||||
@@ -4730,7 +4921,7 @@ function setupCallEventHandler(client) {
|
||||
);
|
||||
}
|
||||
|
||||
call = webRtcCall.createNewMatrixCall(client, event.getRoomId(), {
|
||||
call = createNewMatrixCall(client, event.getRoomId(), {
|
||||
forceTURN: client._forceTURN,
|
||||
});
|
||||
if (!call) {
|
||||
@@ -4830,7 +5021,7 @@ function setupCallEventHandler(client) {
|
||||
// if not live, store the fact that the call has ended because
|
||||
// we're probably getting events backwards so
|
||||
// the hangup will come before the invite
|
||||
call = webRtcCall.createNewMatrixCall(client, event.getRoomId());
|
||||
call = createNewMatrixCall(client, event.getRoomId());
|
||||
if (call) {
|
||||
call.callId = content.call_id;
|
||||
call._initWithHangup(event);
|
||||
@@ -4854,7 +5045,7 @@ function checkTurnServers(client) {
|
||||
return; // guests can't access TURN servers
|
||||
}
|
||||
|
||||
client.turnServer().done(function(res) {
|
||||
client.turnServer().then(function(res) {
|
||||
if (res.uris) {
|
||||
logger.log("Got TURN URIs: " + res.uris + " refresh in " +
|
||||
res.ttl + " secs");
|
||||
@@ -4930,11 +5121,6 @@ MatrixClient.prototype.generateClientSecret = function() {
|
||||
return randomString(32);
|
||||
};
|
||||
|
||||
/** */
|
||||
module.exports.MatrixClient = MatrixClient;
|
||||
/** */
|
||||
module.exports.CRYPTO_ENABLED = CRYPTO_ENABLED;
|
||||
|
||||
// MatrixClient Event JSDocs
|
||||
|
||||
/**
|
||||
@@ -5354,5 +5540,4 @@ module.exports.CRYPTO_ENABLED = CRYPTO_ENABLED;
|
||||
* @property {Function} then promise.then(onFulfilled, onRejected, onProgress)
|
||||
* @property {Function} catch promise.catch(onRejected)
|
||||
* @property {Function} finally promise.finally(callback)
|
||||
* @property {Function} done promise.done(onFulfilled, onRejected, onProgress)
|
||||
*/
|
||||
|
||||
+77
-78
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2019 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.
|
||||
@@ -13,88 +14,86 @@ 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.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
/** @module ContentHelpers */
|
||||
module.exports = {
|
||||
/**
|
||||
* Generates the content for a HTML Message event
|
||||
* @param {string} body the plaintext body of the message
|
||||
* @param {string} htmlBody the HTML representation of the message
|
||||
* @returns {{msgtype: string, format: string, body: string, formatted_body: string}}
|
||||
*/
|
||||
makeHtmlMessage: function(body, htmlBody) {
|
||||
return {
|
||||
msgtype: "m.text",
|
||||
format: "org.matrix.custom.html",
|
||||
body: body,
|
||||
formatted_body: htmlBody,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Generates the content for a HTML Notice event
|
||||
* @param {string} body the plaintext body of the notice
|
||||
* @param {string} htmlBody the HTML representation of the notice
|
||||
* @returns {{msgtype: string, format: string, body: string, formatted_body: string}}
|
||||
*/
|
||||
makeHtmlNotice: function(body, htmlBody) {
|
||||
return {
|
||||
msgtype: "m.notice",
|
||||
format: "org.matrix.custom.html",
|
||||
body: body,
|
||||
formatted_body: htmlBody,
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Generates the content for a HTML Message event
|
||||
* @param {string} body the plaintext body of the message
|
||||
* @param {string} htmlBody the HTML representation of the message
|
||||
* @returns {{msgtype: string, format: string, body: string, formatted_body: string}}
|
||||
*/
|
||||
export function makeHtmlMessage(body, htmlBody) {
|
||||
return {
|
||||
msgtype: "m.text",
|
||||
format: "org.matrix.custom.html",
|
||||
body: body,
|
||||
formatted_body: htmlBody,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the content for a HTML Emote event
|
||||
* @param {string} body the plaintext body of the emote
|
||||
* @param {string} htmlBody the HTML representation of the emote
|
||||
* @returns {{msgtype: string, format: string, body: string, formatted_body: string}}
|
||||
*/
|
||||
makeHtmlEmote: function(body, htmlBody) {
|
||||
return {
|
||||
msgtype: "m.emote",
|
||||
format: "org.matrix.custom.html",
|
||||
body: body,
|
||||
formatted_body: htmlBody,
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Generates the content for a HTML Notice event
|
||||
* @param {string} body the plaintext body of the notice
|
||||
* @param {string} htmlBody the HTML representation of the notice
|
||||
* @returns {{msgtype: string, format: string, body: string, formatted_body: string}}
|
||||
*/
|
||||
export function makeHtmlNotice(body, htmlBody) {
|
||||
return {
|
||||
msgtype: "m.notice",
|
||||
format: "org.matrix.custom.html",
|
||||
body: body,
|
||||
formatted_body: htmlBody,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the content for a Plaintext Message event
|
||||
* @param {string} body the plaintext body of the emote
|
||||
* @returns {{msgtype: string, body: string}}
|
||||
*/
|
||||
makeTextMessage: function(body) {
|
||||
return {
|
||||
msgtype: "m.text",
|
||||
body: body,
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Generates the content for a HTML Emote event
|
||||
* @param {string} body the plaintext body of the emote
|
||||
* @param {string} htmlBody the HTML representation of the emote
|
||||
* @returns {{msgtype: string, format: string, body: string, formatted_body: string}}
|
||||
*/
|
||||
export function makeHtmlEmote(body, htmlBody) {
|
||||
return {
|
||||
msgtype: "m.emote",
|
||||
format: "org.matrix.custom.html",
|
||||
body: body,
|
||||
formatted_body: htmlBody,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the content for a Plaintext Notice event
|
||||
* @param {string} body the plaintext body of the notice
|
||||
* @returns {{msgtype: string, body: string}}
|
||||
*/
|
||||
makeNotice: function(body) {
|
||||
return {
|
||||
msgtype: "m.notice",
|
||||
body: body,
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Generates the content for a Plaintext Message event
|
||||
* @param {string} body the plaintext body of the emote
|
||||
* @returns {{msgtype: string, body: string}}
|
||||
*/
|
||||
export function makeTextMessage(body) {
|
||||
return {
|
||||
msgtype: "m.text",
|
||||
body: body,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the content for a Plaintext Emote event
|
||||
* @param {string} body the plaintext body of the emote
|
||||
* @returns {{msgtype: string, body: string}}
|
||||
*/
|
||||
makeEmoteMessage: function(body) {
|
||||
return {
|
||||
msgtype: "m.emote",
|
||||
body: body,
|
||||
};
|
||||
},
|
||||
};
|
||||
/**
|
||||
* Generates the content for a Plaintext Notice event
|
||||
* @param {string} body the plaintext body of the notice
|
||||
* @returns {{msgtype: string, body: string}}
|
||||
*/
|
||||
export function makeNotice(body) {
|
||||
return {
|
||||
msgtype: "m.notice",
|
||||
body: body,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the content for a Plaintext Emote event
|
||||
* @param {string} body the plaintext body of the emote
|
||||
* @returns {{msgtype: string, body: string}}
|
||||
*/
|
||||
export function makeEmoteMessage(body) {
|
||||
return {
|
||||
msgtype: "m.emote",
|
||||
body: body,
|
||||
};
|
||||
}
|
||||
|
||||
+84
-85
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2019 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.
|
||||
@@ -16,95 +17,93 @@ limitations under the License.
|
||||
/**
|
||||
* @module content-repo
|
||||
*/
|
||||
const utils = require("./utils");
|
||||
|
||||
/** Content Repo utility functions */
|
||||
module.exports = {
|
||||
/**
|
||||
* Get the HTTP URL for an MXC URI.
|
||||
* @param {string} baseUrl The base homeserver url which has a content repo.
|
||||
* @param {string} mxc The mxc:// URI.
|
||||
* @param {Number} width The desired width of the thumbnail.
|
||||
* @param {Number} height The desired height of the thumbnail.
|
||||
* @param {string} resizeMethod The thumbnail resize method to use, either
|
||||
* "crop" or "scale".
|
||||
* @param {Boolean} allowDirectLinks If true, return any non-mxc URLs
|
||||
* directly. Fetching such URLs will leak information about the user to
|
||||
* anyone they share a room with. If false, will return the emptry string
|
||||
* for such URLs.
|
||||
* @return {string} The complete URL to the content.
|
||||
*/
|
||||
getHttpUriForMxc: function(baseUrl, mxc, width, height,
|
||||
resizeMethod, allowDirectLinks) {
|
||||
if (typeof mxc !== "string" || !mxc) {
|
||||
import * as utils from "./utils";
|
||||
|
||||
/**
|
||||
* Get the HTTP URL for an MXC URI.
|
||||
* @param {string} baseUrl The base homeserver url which has a content repo.
|
||||
* @param {string} mxc The mxc:// URI.
|
||||
* @param {Number} width The desired width of the thumbnail.
|
||||
* @param {Number} height The desired height of the thumbnail.
|
||||
* @param {string} resizeMethod The thumbnail resize method to use, either
|
||||
* "crop" or "scale".
|
||||
* @param {Boolean} allowDirectLinks If true, return any non-mxc URLs
|
||||
* directly. Fetching such URLs will leak information about the user to
|
||||
* anyone they share a room with. If false, will return the emptry string
|
||||
* for such URLs.
|
||||
* @return {string} The complete URL to the content.
|
||||
*/
|
||||
export function getHttpUriForMxc(baseUrl, mxc, width, height,
|
||||
resizeMethod, allowDirectLinks) {
|
||||
if (typeof mxc !== "string" || !mxc) {
|
||||
return '';
|
||||
}
|
||||
if (mxc.indexOf("mxc://") !== 0) {
|
||||
if (allowDirectLinks) {
|
||||
return mxc;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
if (mxc.indexOf("mxc://") !== 0) {
|
||||
if (allowDirectLinks) {
|
||||
return mxc;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
let serverAndMediaId = mxc.slice(6); // strips mxc://
|
||||
let prefix = "/_matrix/media/r0/download/";
|
||||
const params = {};
|
||||
}
|
||||
let serverAndMediaId = mxc.slice(6); // strips mxc://
|
||||
let prefix = "/_matrix/media/r0/download/";
|
||||
const params = {};
|
||||
|
||||
if (width) {
|
||||
params.width = Math.round(width);
|
||||
}
|
||||
if (height) {
|
||||
params.height = Math.round(height);
|
||||
}
|
||||
if (resizeMethod) {
|
||||
params.method = resizeMethod;
|
||||
}
|
||||
if (utils.keys(params).length > 0) {
|
||||
// these are thumbnailing params so they probably want the
|
||||
// thumbnailing API...
|
||||
prefix = "/_matrix/media/r0/thumbnail/";
|
||||
}
|
||||
if (width) {
|
||||
params.width = Math.round(width);
|
||||
}
|
||||
if (height) {
|
||||
params.height = Math.round(height);
|
||||
}
|
||||
if (resizeMethod) {
|
||||
params.method = resizeMethod;
|
||||
}
|
||||
if (utils.keys(params).length > 0) {
|
||||
// these are thumbnailing params so they probably want the
|
||||
// thumbnailing API...
|
||||
prefix = "/_matrix/media/r0/thumbnail/";
|
||||
}
|
||||
|
||||
const fragmentOffset = serverAndMediaId.indexOf("#");
|
||||
let fragment = "";
|
||||
if (fragmentOffset >= 0) {
|
||||
fragment = serverAndMediaId.substr(fragmentOffset);
|
||||
serverAndMediaId = serverAndMediaId.substr(0, fragmentOffset);
|
||||
}
|
||||
return baseUrl + prefix + serverAndMediaId +
|
||||
(utils.keys(params).length === 0 ? "" :
|
||||
("?" + utils.encodeParams(params))) + fragment;
|
||||
},
|
||||
const fragmentOffset = serverAndMediaId.indexOf("#");
|
||||
let fragment = "";
|
||||
if (fragmentOffset >= 0) {
|
||||
fragment = serverAndMediaId.substr(fragmentOffset);
|
||||
serverAndMediaId = serverAndMediaId.substr(0, fragmentOffset);
|
||||
}
|
||||
return baseUrl + prefix + serverAndMediaId +
|
||||
(utils.keys(params).length === 0 ? "" :
|
||||
("?" + utils.encodeParams(params))) + fragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an identicon URL from an arbitrary string.
|
||||
* @param {string} baseUrl The base homeserver url which has a content repo.
|
||||
* @param {string} identiconString The string to create an identicon for.
|
||||
* @param {Number} width The desired width of the image in pixels. Default: 96.
|
||||
* @param {Number} height The desired height of the image in pixels. Default: 96.
|
||||
* @return {string} The complete URL to the identicon.
|
||||
* @deprecated This is no longer in the specification.
|
||||
*/
|
||||
getIdenticonUri: function(baseUrl, identiconString, width, height) {
|
||||
if (!identiconString) {
|
||||
return null;
|
||||
}
|
||||
if (!width) {
|
||||
width = 96;
|
||||
}
|
||||
if (!height) {
|
||||
height = 96;
|
||||
}
|
||||
const params = {
|
||||
width: width,
|
||||
height: height,
|
||||
};
|
||||
/**
|
||||
* Get an identicon URL from an arbitrary string.
|
||||
* @param {string} baseUrl The base homeserver url which has a content repo.
|
||||
* @param {string} identiconString The string to create an identicon for.
|
||||
* @param {Number} width The desired width of the image in pixels. Default: 96.
|
||||
* @param {Number} height The desired height of the image in pixels. Default: 96.
|
||||
* @return {string} The complete URL to the identicon.
|
||||
* @deprecated This is no longer in the specification.
|
||||
*/
|
||||
export function getIdenticonUri(baseUrl, identiconString, width, height) {
|
||||
if (!identiconString) {
|
||||
return null;
|
||||
}
|
||||
if (!width) {
|
||||
width = 96;
|
||||
}
|
||||
if (!height) {
|
||||
height = 96;
|
||||
}
|
||||
const params = {
|
||||
width: width,
|
||||
height: height,
|
||||
};
|
||||
|
||||
const path = utils.encodeUri("/_matrix/media/unstable/identicon/$ident", {
|
||||
$ident: identiconString,
|
||||
});
|
||||
return baseUrl + path +
|
||||
(utils.keys(params).length === 0 ? "" :
|
||||
("?" + utils.encodeParams(params)));
|
||||
},
|
||||
};
|
||||
const path = utils.encodeUri("/_matrix/media/unstable/identicon/$ident", {
|
||||
$ident: identiconString,
|
||||
});
|
||||
return baseUrl + path +
|
||||
(utils.keys(params).length === 0 ? "" :
|
||||
("?" + utils.encodeParams(params)));
|
||||
}
|
||||
|
||||
+68
-16
@@ -20,12 +20,15 @@ limitations under the License.
|
||||
* @module crypto/CrossSigning
|
||||
*/
|
||||
|
||||
import {pkSign, pkVerify} from './olmlib';
|
||||
import {decodeBase64, encodeBase64, pkSign, pkVerify} from './olmlib';
|
||||
import {EventEmitter} from 'events';
|
||||
import logger from '../logger';
|
||||
import {logger} from '../logger';
|
||||
|
||||
function publicKeyFromKeyInfo(keyInfo) {
|
||||
return Object.entries(keyInfo.keys)[0];
|
||||
// `keys` is an object with { [`ed25519:${pubKey}`]: pubKey }
|
||||
// We assume only a single key, and we want the bare form without type
|
||||
// prefix, so we select the values.
|
||||
return Object.values(keyInfo.keys)[0];
|
||||
}
|
||||
|
||||
export class CrossSigningInfo extends EventEmitter {
|
||||
@@ -54,8 +57,9 @@ export class CrossSigningInfo extends EventEmitter {
|
||||
/**
|
||||
* Calls the app callback to ask for a private key
|
||||
* @param {string} type The key type ("master", "self_signing", or "user_signing")
|
||||
* @param {Uint8Array} expectedPubkey The matching public key or undefined to use
|
||||
* @param {string} expectedPubkey The matching public key or undefined to use
|
||||
* the stored public key for the given key type.
|
||||
* @returns {Array} An array with [ public key, Olm.PkSigning ]
|
||||
*/
|
||||
async getCrossSigningKey(type, expectedPubkey) {
|
||||
if (!this._callbacks.getCrossSigningKey) {
|
||||
@@ -69,7 +73,7 @@ export class CrossSigningInfo extends EventEmitter {
|
||||
const privkey = await this._callbacks.getCrossSigningKey(type, expectedPubkey);
|
||||
if (!privkey) {
|
||||
throw new Error(
|
||||
"getCrossSigningKey callback for " + type + " returned falsey",
|
||||
"getCrossSigningKey callback for " + type + " returned falsey",
|
||||
);
|
||||
}
|
||||
const signing = new global.Olm.PkSigning();
|
||||
@@ -101,12 +105,54 @@ export class CrossSigningInfo extends EventEmitter {
|
||||
};
|
||||
}
|
||||
|
||||
hasKeys() {
|
||||
return Object.keys(this.keys).length > 0;
|
||||
/**
|
||||
* Check whether the private keys exist in secret storage.
|
||||
* XXX: This could be static, be we often seem to have an instance when we
|
||||
* want to know this anyway...
|
||||
*
|
||||
* @param {SecretStorage} secretStorage The secret store using account data
|
||||
* @returns {boolean} Whether all private keys were found in storage
|
||||
*/
|
||||
async isStoredInSecretStorage(secretStorage) {
|
||||
let stored = true;
|
||||
for (const type of ["master", "self_signing", "user_signing"]) {
|
||||
stored &= await secretStorage.isStored(`m.cross_signing.${type}`, false);
|
||||
}
|
||||
return stored;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ID used to identify the user
|
||||
* Store private keys in secret storage for use by other devices. This is
|
||||
* typically called in conjunction with the creation of new cross-signing
|
||||
* keys.
|
||||
*
|
||||
* @param {object} keys The keys to store
|
||||
* @param {SecretStorage} secretStorage The secret store using account data
|
||||
*/
|
||||
static async storeInSecretStorage(keys, secretStorage) {
|
||||
for (const type of Object.keys(keys)) {
|
||||
const encodedKey = encodeBase64(keys[type]);
|
||||
await secretStorage.store(`m.cross_signing.${type}`, encodedKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get private keys from secret storage created by some other device. This
|
||||
* also passes the private keys to the app-specific callback.
|
||||
*
|
||||
* @param {string} type The type of key to get. One of "master",
|
||||
* "self_signing", or "user_signing".
|
||||
* @param {SecretStorage} secretStorage The secret store using account data
|
||||
* @return {Uint8Array} The private key
|
||||
*/
|
||||
static async getFromSecretStorage(type, secretStorage) {
|
||||
const encodedKey = await secretStorage.get(`m.cross_signing.${type}`);
|
||||
return decodeBase64(encodedKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ID used to identify the user. This can also be used to test for
|
||||
* the existence of a given key type.
|
||||
*
|
||||
* @param {string} type The type of key to get the ID of. One of "master",
|
||||
* "self_signing", or "user_signing". Defaults to "master".
|
||||
@@ -117,10 +163,16 @@ export class CrossSigningInfo extends EventEmitter {
|
||||
type = type || "master";
|
||||
if (!this.keys[type]) return null;
|
||||
const keyInfo = this.keys[type];
|
||||
return publicKeyFromKeyInfo(keyInfo)[1];
|
||||
return publicKeyFromKeyInfo(keyInfo);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create new cross-signing keys for the given key types. The public keys
|
||||
* will be held in this class, while the private keys are passed off to the
|
||||
* `saveCrossSigningKeys` application callback.
|
||||
*
|
||||
* @param {CrossSigningLevel} level The key types to reset
|
||||
*/
|
||||
async resetKeys(level) {
|
||||
if (!this._callbacks.saveCrossSigningKeys) {
|
||||
throw new Error("No saveCrossSigningKeys callback supplied");
|
||||
@@ -159,7 +211,7 @@ export class CrossSigningInfo extends EventEmitter {
|
||||
},
|
||||
};
|
||||
} else {
|
||||
[masterPub, masterSigning] = await this.getCrossSigningyKey("master");
|
||||
[masterPub, masterSigning] = await this.getCrossSigningKey("master");
|
||||
}
|
||||
|
||||
if (level & CrossSigningLevel.SELF_SIGNING) {
|
||||
@@ -219,7 +271,7 @@ export class CrossSigningInfo extends EventEmitter {
|
||||
if (!this.keys.master) {
|
||||
// this is the first key we've seen, so first-use is true
|
||||
this.firstUse = true;
|
||||
} else if (publicKeyFromKeyInfo(keys.master)[1] !== this.getId()) {
|
||||
} else if (publicKeyFromKeyInfo(keys.master) !== this.getId()) {
|
||||
// this is a different key, so first-use is false
|
||||
this.firstUse = false;
|
||||
} // otherwise, same key, so no change
|
||||
@@ -229,7 +281,7 @@ export class CrossSigningInfo extends EventEmitter {
|
||||
} else {
|
||||
throw new Error("Tried to set cross-signing keys without a master key");
|
||||
}
|
||||
const masterKey = publicKeyFromKeyInfo(signingKeys.master)[1];
|
||||
const masterKey = publicKeyFromKeyInfo(signingKeys.master);
|
||||
|
||||
// verify signatures
|
||||
if (keys.user_signing) {
|
||||
@@ -268,8 +320,8 @@ export class CrossSigningInfo extends EventEmitter {
|
||||
this.keys.master = keys.master;
|
||||
// if the master key is set, then the old self-signing and
|
||||
// user-signing keys are obsolete
|
||||
delete this.keys.self_signing;
|
||||
delete this.keys.user_signing;
|
||||
this.keys.self_signing = null;
|
||||
this.keys.user_signing = null;
|
||||
}
|
||||
if (keys.self_signing) {
|
||||
this.keys.self_signing = keys.self_signing;
|
||||
@@ -381,7 +433,7 @@ export class CrossSigningInfo extends EventEmitter {
|
||||
pkVerify(userSSK, userCrossSigning.getId(), userCrossSigning.userId);
|
||||
// ...and this device's key from their SSK...
|
||||
pkVerify(
|
||||
deviceObj, publicKeyFromKeyInfo(userSSK)[1], userCrossSigning.userId,
|
||||
deviceObj, publicKeyFromKeyInfo(userSSK), userCrossSigning.userId,
|
||||
);
|
||||
// ...then we trust this device as much as far as we trust the user
|
||||
return DeviceTrustLevel.fromUserTrustLevel(userTrust, localTrust);
|
||||
|
||||
+11
-13
@@ -15,7 +15,6 @@ 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.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* @module crypto/DeviceList
|
||||
@@ -23,15 +22,13 @@ limitations under the License.
|
||||
* Manages the list of other users' devices
|
||||
*/
|
||||
|
||||
import Promise from 'bluebird';
|
||||
import {EventEmitter} from 'events';
|
||||
|
||||
import logger from '../logger';
|
||||
import DeviceInfo from './deviceinfo';
|
||||
import {logger} from '../logger';
|
||||
import {DeviceInfo} from './deviceinfo';
|
||||
import {CrossSigningInfo} from './CrossSigning';
|
||||
import olmlib from './olmlib';
|
||||
import IndexedDBCryptoStore from './store/indexeddb-crypto-store';
|
||||
import {sleep} from '../utils';
|
||||
import * as olmlib from './olmlib';
|
||||
import {IndexedDBCryptoStore} from './store/indexeddb-crypto-store';
|
||||
import {defer, sleep} from '../utils';
|
||||
|
||||
|
||||
/* State transition diagram for DeviceList._deviceTrackingStatus
|
||||
@@ -64,7 +61,7 @@ const TRACKING_STATUS_UP_TO_DATE = 3;
|
||||
/**
|
||||
* @alias module:crypto/DeviceList
|
||||
*/
|
||||
export default class DeviceList extends EventEmitter {
|
||||
export class DeviceList extends EventEmitter {
|
||||
constructor(baseApis, cryptoStore, olmDevice) {
|
||||
super();
|
||||
|
||||
@@ -122,7 +119,8 @@ export default class DeviceList extends EventEmitter {
|
||||
'readonly', [IndexedDBCryptoStore.STORE_DEVICE_DATA], (txn) => {
|
||||
this._cryptoStore.getEndToEndDeviceData(txn, (deviceData) => {
|
||||
this._devices = deviceData ? deviceData.devices : {},
|
||||
this._ssks = deviceData ? deviceData.self_signing_keys || {} : {};
|
||||
this._crossSigningInfo = deviceData ?
|
||||
deviceData.crossSigningInfo || {} : {};
|
||||
this._deviceTrackingStatus = deviceData ?
|
||||
deviceData.trackingStatus : {};
|
||||
this._syncToken = deviceData ? deviceData.syncToken : null;
|
||||
@@ -213,7 +211,7 @@ export default class DeviceList extends EventEmitter {
|
||||
'readwrite', [IndexedDBCryptoStore.STORE_DEVICE_DATA], (txn) => {
|
||||
this._cryptoStore.storeEndToEndDeviceData({
|
||||
devices: this._devices,
|
||||
self_signing_keys: this._ssks,
|
||||
crossSigningInfo: this._crossSigningInfo,
|
||||
trackingStatus: this._deviceTrackingStatus,
|
||||
syncToken: this._syncToken,
|
||||
}, txn);
|
||||
@@ -711,7 +709,7 @@ class DeviceListUpdateSerialiser {
|
||||
});
|
||||
|
||||
if (!this._queuedQueryDeferred) {
|
||||
this._queuedQueryDeferred = Promise.defer();
|
||||
this._queuedQueryDeferred = defer();
|
||||
}
|
||||
|
||||
// We always take the new sync token and just use the latest one we've
|
||||
@@ -776,7 +774,7 @@ class DeviceListUpdateSerialiser {
|
||||
}
|
||||
|
||||
return prom;
|
||||
}).done(() => {
|
||||
}).then(() => {
|
||||
logger.log('Completed key download for ' + downloadUsers);
|
||||
|
||||
this._downloadInProgress = false;
|
||||
|
||||
+127
-16
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
Copyright 2017, 2019 New Vector Ltd
|
||||
Copyright 2019, 2020 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.
|
||||
@@ -15,8 +16,9 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import logger from '../logger';
|
||||
import IndexedDBCryptoStore from './store/indexeddb-crypto-store';
|
||||
import {logger} from '../logger';
|
||||
import {IndexedDBCryptoStore} from './store/indexeddb-crypto-store';
|
||||
import * as algorithms from './algorithms';
|
||||
|
||||
// The maximum size of an event is 65K, and we base64 the content, so this is a
|
||||
// reasonable approximation to the biggest plaintext we can encrypt.
|
||||
@@ -69,7 +71,7 @@ function checkPayloadLength(payloadString) {
|
||||
* @property {string} deviceCurve25519Key Curve25519 key for the account
|
||||
* @property {string} deviceEd25519Key Ed25519 key for the account
|
||||
*/
|
||||
function OlmDevice(cryptoStore) {
|
||||
export function OlmDevice(cryptoStore) {
|
||||
this._cryptoStore = cryptoStore;
|
||||
this._pickleKey = "DEFAULT_KEY";
|
||||
|
||||
@@ -671,6 +673,18 @@ OlmDevice.prototype.matchesSession = async function(
|
||||
return matches;
|
||||
};
|
||||
|
||||
OlmDevice.prototype.recordSessionProblem = async function(deviceKey, type, fixed) {
|
||||
await this._cryptoStore.storeEndToEndSessionProblem(deviceKey, type, fixed);
|
||||
};
|
||||
|
||||
OlmDevice.prototype.sessionMayHaveProblems = async function(deviceKey, timestamp) {
|
||||
return await this._cryptoStore.getEndToEndSessionProblem(deviceKey, timestamp);
|
||||
};
|
||||
|
||||
OlmDevice.prototype.filterOutNotifiedErrorDevices = async function(devices) {
|
||||
return await this._cryptoStore.filterOutNotifiedErrorDevices(devices);
|
||||
};
|
||||
|
||||
|
||||
// Outbound group session
|
||||
// ======================
|
||||
@@ -818,9 +832,9 @@ OlmDevice.prototype._getInboundGroupSession = function(
|
||||
roomId, senderKey, sessionId, txn, func,
|
||||
) {
|
||||
this._cryptoStore.getEndToEndInboundGroupSession(
|
||||
senderKey, sessionId, txn, (sessionData) => {
|
||||
senderKey, sessionId, txn, (sessionData, withheld) => {
|
||||
if (sessionData === null) {
|
||||
func(null);
|
||||
func(null, null, withheld);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -834,7 +848,7 @@ OlmDevice.prototype._getInboundGroupSession = function(
|
||||
}
|
||||
|
||||
this._unpickleInboundGroupSession(sessionData, (session) => {
|
||||
func(session, sessionData);
|
||||
func(session, sessionData, withheld);
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -859,7 +873,10 @@ OlmDevice.prototype.addInboundGroupSession = async function(
|
||||
exportFormat,
|
||||
) {
|
||||
await this._cryptoStore.doTxn(
|
||||
'readwrite', [IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS], (txn) => {
|
||||
'readwrite', [
|
||||
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS,
|
||||
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS_WITHHELD,
|
||||
], (txn) => {
|
||||
/* if we already have this session, consider updating it */
|
||||
this._getInboundGroupSession(
|
||||
roomId, senderKey, sessionId, txn,
|
||||
@@ -914,6 +931,60 @@ OlmDevice.prototype.addInboundGroupSession = async function(
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Record in the data store why an inbound group session was withheld.
|
||||
*
|
||||
* @param {string} roomId room that the session belongs to
|
||||
* @param {string} senderKey base64-encoded curve25519 key of the sender
|
||||
* @param {string} sessionId session identifier
|
||||
* @param {string} code reason code
|
||||
* @param {string} reason human-readable version of `code`
|
||||
*/
|
||||
OlmDevice.prototype.addInboundGroupSessionWithheld = async function(
|
||||
roomId, senderKey, sessionId, code, reason,
|
||||
) {
|
||||
await this._cryptoStore.doTxn(
|
||||
'readwrite', [IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS_WITHHELD],
|
||||
(txn) => {
|
||||
this._cryptoStore.storeEndToEndInboundGroupSessionWithheld(
|
||||
senderKey, sessionId,
|
||||
{
|
||||
room_id: roomId,
|
||||
code: code,
|
||||
reason: reason,
|
||||
},
|
||||
txn,
|
||||
);
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export const WITHHELD_MESSAGES = {
|
||||
"m.unverified": "The sender has disabled encrypting to unverified devices.",
|
||||
"m.blacklisted": "The sender has blocked you.",
|
||||
"m.unauthorised": "You are not authorised to read the message.",
|
||||
"m.no_olm": "Unable to establish a secure channel.",
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate the message to use for the exception when a session key is withheld.
|
||||
*
|
||||
* @param {object} withheld An object that describes why the key was withheld.
|
||||
*
|
||||
* @return {string} the message
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function _calculateWithheldMessage(withheld) {
|
||||
if (withheld.code && withheld.code in WITHHELD_MESSAGES) {
|
||||
return WITHHELD_MESSAGES[withheld.code];
|
||||
} else if (withheld.reason) {
|
||||
return withheld.reason;
|
||||
} else {
|
||||
return "decryption key withheld";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt a received message with an inbound group session
|
||||
*
|
||||
@@ -934,16 +1005,49 @@ OlmDevice.prototype.decryptGroupMessage = async function(
|
||||
roomId, senderKey, sessionId, body, eventId, timestamp,
|
||||
) {
|
||||
let result;
|
||||
// when the localstorage crypto store is used as an indexeddb backend,
|
||||
// exceptions thrown from within the inner function are not passed through
|
||||
// to the top level, so we store exceptions in a variable and raise them at
|
||||
// the end
|
||||
let error;
|
||||
|
||||
await this._cryptoStore.doTxn(
|
||||
'readwrite', [IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS], (txn) => {
|
||||
'readwrite', [
|
||||
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS,
|
||||
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS_WITHHELD,
|
||||
], (txn) => {
|
||||
this._getInboundGroupSession(
|
||||
roomId, senderKey, sessionId, txn, (session, sessionData) => {
|
||||
roomId, senderKey, sessionId, txn, (session, sessionData, withheld) => {
|
||||
if (session === null) {
|
||||
if (withheld) {
|
||||
error = new algorithms.DecryptionError(
|
||||
"MEGOLM_UNKNOWN_INBOUND_SESSION_ID",
|
||||
_calculateWithheldMessage(withheld),
|
||||
{
|
||||
session: senderKey + '|' + sessionId,
|
||||
},
|
||||
);
|
||||
}
|
||||
result = null;
|
||||
return;
|
||||
}
|
||||
const res = session.decrypt(body);
|
||||
let res;
|
||||
try {
|
||||
res = session.decrypt(body);
|
||||
} catch (e) {
|
||||
if (e && e.message === 'OLM.UNKNOWN_MESSAGE_INDEX' && withheld) {
|
||||
error = new algorithms.DecryptionError(
|
||||
"MEGOLM_UNKNOWN_INBOUND_SESSION_ID",
|
||||
_calculateWithheldMessage(withheld),
|
||||
{
|
||||
session: senderKey + '|' + sessionId,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
error = e;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let plaintext = res.plaintext;
|
||||
if (plaintext === undefined) {
|
||||
@@ -965,10 +1069,11 @@ OlmDevice.prototype.decryptGroupMessage = async function(
|
||||
msgInfo.id !== eventId ||
|
||||
msgInfo.timestamp !== timestamp
|
||||
) {
|
||||
throw new Error(
|
||||
error = new Error(
|
||||
"Duplicate message index, possible replay attack: " +
|
||||
messageIndexKey,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this._inboundGroupSessionMessageIndexes[messageIndexKey] = {
|
||||
@@ -994,6 +1099,9 @@ OlmDevice.prototype.decryptGroupMessage = async function(
|
||||
},
|
||||
);
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -1009,7 +1117,10 @@ OlmDevice.prototype.decryptGroupMessage = async function(
|
||||
OlmDevice.prototype.hasInboundSessionKeys = async function(roomId, senderKey, sessionId) {
|
||||
let result;
|
||||
await this._cryptoStore.doTxn(
|
||||
'readonly', [IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS], (txn) => {
|
||||
'readonly', [
|
||||
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS,
|
||||
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS_WITHHELD,
|
||||
], (txn) => {
|
||||
this._cryptoStore.getEndToEndInboundGroupSession(
|
||||
senderKey, sessionId, txn, (sessionData) => {
|
||||
if (sessionData === null) {
|
||||
@@ -1060,7 +1171,10 @@ OlmDevice.prototype.getInboundGroupSessionKey = async function(
|
||||
) {
|
||||
let result;
|
||||
await this._cryptoStore.doTxn(
|
||||
'readonly', [IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS], (txn) => {
|
||||
'readonly', [
|
||||
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS,
|
||||
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS_WITHHELD,
|
||||
], (txn) => {
|
||||
this._getInboundGroupSession(
|
||||
roomId, senderKey, sessionId, txn, (session, sessionData) => {
|
||||
if (session === null) {
|
||||
@@ -1139,6 +1253,3 @@ OlmDevice.prototype.verifySignature = function(
|
||||
util.ed25519_verify(key, message, signature);
|
||||
});
|
||||
};
|
||||
|
||||
/** */
|
||||
module.exports = OlmDevice;
|
||||
|
||||
@@ -14,10 +14,8 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import Promise from 'bluebird';
|
||||
|
||||
import logger from '../logger';
|
||||
import utils from '../utils';
|
||||
import {logger} from '../logger';
|
||||
import * as utils from '../utils';
|
||||
|
||||
/**
|
||||
* Internal module. Management of outgoing room key requests.
|
||||
@@ -77,7 +75,7 @@ const ROOM_KEY_REQUEST_STATES = {
|
||||
CANCELLATION_PENDING_AND_WILL_RESEND: 3,
|
||||
};
|
||||
|
||||
export default class OutgoingRoomKeyRequestManager {
|
||||
export class OutgoingRoomKeyRequestManager {
|
||||
constructor(baseApis, deviceId, cryptoStore) {
|
||||
this._baseApis = baseApis;
|
||||
this._deviceId = deviceId;
|
||||
|
||||
@@ -20,12 +20,12 @@ limitations under the License.
|
||||
* Manages the list of encrypted rooms
|
||||
*/
|
||||
|
||||
import IndexedDBCryptoStore from './store/indexeddb-crypto-store';
|
||||
import {IndexedDBCryptoStore} from './store/indexeddb-crypto-store';
|
||||
|
||||
/**
|
||||
* @alias module:crypto/RoomList
|
||||
*/
|
||||
export default class RoomList {
|
||||
export class RoomList {
|
||||
constructor(cryptoStore) {
|
||||
this._cryptoStore = cryptoStore;
|
||||
|
||||
|
||||
+117
-61
@@ -15,12 +15,10 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import {EventEmitter} from 'events';
|
||||
import logger from '../logger';
|
||||
import olmlib from './olmlib';
|
||||
import { randomString } from '../randomstring';
|
||||
import { keyFromPassphrase } from './key_passphrase';
|
||||
import { encodeRecoveryKey } from './recoverykey';
|
||||
import { pkVerify } from './olmlib';
|
||||
import {logger} from '../logger';
|
||||
import * as olmlib from './olmlib';
|
||||
import {pkVerify} from './olmlib';
|
||||
import {randomString} from '../randomstring';
|
||||
|
||||
export const SECRET_STORAGE_ALGORITHM_V1 = "m.secret_storage.v1.curve25519-aes-sha2";
|
||||
|
||||
@@ -28,7 +26,7 @@ export const SECRET_STORAGE_ALGORITHM_V1 = "m.secret_storage.v1.curve25519-aes-s
|
||||
* Implements Secure Secret Storage and Sharing (MSC1946)
|
||||
* @module crypto/SecretStorage
|
||||
*/
|
||||
export default class SecretStorage extends EventEmitter {
|
||||
export class SecretStorage extends EventEmitter {
|
||||
constructor(baseApis, cryptoCallbacks, crossSigningInfo) {
|
||||
super();
|
||||
this._baseApis = baseApis;
|
||||
@@ -38,12 +36,12 @@ export default class SecretStorage extends EventEmitter {
|
||||
this._incomingRequests = {};
|
||||
}
|
||||
|
||||
getDefaultKeyId() {
|
||||
const defaultKeyEvent = this._baseApis.getAccountData(
|
||||
async getDefaultKeyId() {
|
||||
const defaultKey = await this._baseApis.getAccountDataFromServer(
|
||||
'm.secret_storage.default_key',
|
||||
);
|
||||
if (!defaultKeyEvent) return null;
|
||||
return defaultKeyEvent.getContent().key;
|
||||
if (!defaultKey) return null;
|
||||
return defaultKey.key;
|
||||
}
|
||||
|
||||
setDefaultKeyId(keyId) {
|
||||
@@ -71,8 +69,7 @@ export default class SecretStorage extends EventEmitter {
|
||||
*
|
||||
* @param {string} algorithm the algorithm used by the key.
|
||||
* @param {object} opts the options for the algorithm. The properties used
|
||||
* depend on the algorithm given. This object may be modified to pass
|
||||
* information back about the key.
|
||||
* depend on the algorithm given.
|
||||
* @param {string} [keyId] the ID of the key. If not given, a random
|
||||
* ID will be generated.
|
||||
*
|
||||
@@ -92,21 +89,16 @@ export default class SecretStorage extends EventEmitter {
|
||||
{
|
||||
const decryption = new global.Olm.PkDecryption();
|
||||
try {
|
||||
if (opts.passphrase) {
|
||||
const key = await keyFromPassphrase(opts.passphrase);
|
||||
keyData.passphrase = {
|
||||
algorithm: "m.pbkdf2",
|
||||
iterations: key.iterations,
|
||||
salt: key.salt,
|
||||
};
|
||||
opts.encodedkey = encodeRecoveryKey(key.key);
|
||||
keyData.pubkey = decryption.init_with_private_key(key.key);
|
||||
} else if (opts.privkey) {
|
||||
keyData.pubkey = decryption.init_with_private_key(opts.privkey);
|
||||
opts.encodedkey = encodeRecoveryKey(opts.privkey);
|
||||
const { passphrase, pubkey } = opts;
|
||||
// Copies in public key details of the form generated by
|
||||
// the Crypto module's `createRecoveryKeyFromPassphrase`.
|
||||
if (passphrase && pubkey) {
|
||||
keyData.passphrase = passphrase;
|
||||
keyData.pubkey = pubkey;
|
||||
} else if (pubkey) {
|
||||
keyData.pubkey = pubkey;
|
||||
} else {
|
||||
keyData.pubkey = decryption.generate_key();
|
||||
opts.encodedkey = encodeRecoveryKey(decryption.get_private_key());
|
||||
}
|
||||
} finally {
|
||||
decryption.free();
|
||||
@@ -120,7 +112,11 @@ export default class SecretStorage extends EventEmitter {
|
||||
if (!keyId) {
|
||||
do {
|
||||
keyId = randomString(32);
|
||||
} while (this._baseApis.getAccountData(`m.secret_storage.key.${keyId}`));
|
||||
} while (
|
||||
await this._baseApis.getAccountDataFromServer(
|
||||
`m.secret_storage.key.${keyId}`,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
await this._crossSigningInfo.signObject(keyData, 'master');
|
||||
@@ -132,7 +128,52 @@ export default class SecretStorage extends EventEmitter {
|
||||
return keyId;
|
||||
}
|
||||
|
||||
// TODO: need a function to get all the secret keys
|
||||
/**
|
||||
* Signs a given secret storage key with the cross-signing master key.
|
||||
*
|
||||
* @param {string} [keyId = default key's ID] The ID of the key to sign.
|
||||
* Defaults to the default key ID if not provided.
|
||||
*/
|
||||
async signKey(keyId) {
|
||||
if (!keyId) {
|
||||
keyId = await this.getDefaultKeyId();
|
||||
}
|
||||
if (!keyId) {
|
||||
throw new Error("signKey requires a key ID");
|
||||
}
|
||||
|
||||
const keyInfo = await this._baseApis.getAccountDataFromServer(
|
||||
`m.secret_storage.key.${keyId}`,
|
||||
);
|
||||
if (!keyInfo) {
|
||||
throw new Error(`Key ${keyId} does not exist in account data`);
|
||||
}
|
||||
|
||||
await this._crossSigningInfo.signObject(keyInfo, 'master');
|
||||
await this._baseApis.setAccountData(
|
||||
`m.secret_storage.key.${keyId}`, keyInfo,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether we have a key with a given ID.
|
||||
*
|
||||
* @param {string} [keyId = default key's ID] The ID of the key to check
|
||||
* for. Defaults to the default key ID if not provided.
|
||||
* @return {boolean} Whether we have the key.
|
||||
*/
|
||||
async hasKey(keyId) {
|
||||
if (!keyId) {
|
||||
keyId = await this.getDefaultKeyId();
|
||||
}
|
||||
if (!keyId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !!this._baseApis.getAccountDataFromServer(
|
||||
"m.secret_storage.key." + keyId,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store an encrypted secret on the server
|
||||
@@ -146,7 +187,7 @@ export default class SecretStorage extends EventEmitter {
|
||||
const encrypted = {};
|
||||
|
||||
if (!keys) {
|
||||
const defaultKeyId = this.getDefaultKeyId();
|
||||
const defaultKeyId = await this.getDefaultKeyId();
|
||||
if (!defaultKeyId) {
|
||||
throw new Error("No keys specified and no default key present");
|
||||
}
|
||||
@@ -159,28 +200,27 @@ export default class SecretStorage extends EventEmitter {
|
||||
|
||||
for (const keyId of keys) {
|
||||
// get key information from key storage
|
||||
const keyInfo = this._baseApis.getAccountData(
|
||||
const keyInfo = await this._baseApis.getAccountDataFromServer(
|
||||
"m.secret_storage.key." + keyId,
|
||||
);
|
||||
if (!keyInfo) {
|
||||
throw new Error("Unknown key: " + keyId);
|
||||
}
|
||||
const keyInfoContent = keyInfo.getContent();
|
||||
|
||||
// check signature of key info
|
||||
pkVerify(
|
||||
keyInfoContent,
|
||||
keyInfo,
|
||||
this._crossSigningInfo.getId('master'),
|
||||
this._crossSigningInfo.userId,
|
||||
);
|
||||
|
||||
// encrypt secret, based on the algorithm
|
||||
switch (keyInfoContent.algorithm) {
|
||||
switch (keyInfo.algorithm) {
|
||||
case SECRET_STORAGE_ALGORITHM_V1:
|
||||
{
|
||||
const encryption = new global.Olm.PkEncryption();
|
||||
try {
|
||||
encryption.set_recipient_key(keyInfoContent.pubkey);
|
||||
encryption.set_recipient_key(keyInfo.pubkey);
|
||||
encrypted[keyId] = encryption.encrypt(secret);
|
||||
} finally {
|
||||
encryption.free();
|
||||
@@ -189,7 +229,7 @@ export default class SecretStorage extends EventEmitter {
|
||||
}
|
||||
default:
|
||||
logger.warn("unknown algorithm for secret storage key " + keyId
|
||||
+ ": " + keyInfoContent.algorithm);
|
||||
+ ": " + keyInfo.algorithm);
|
||||
// do nothing if we don't understand the encryption algorithm
|
||||
}
|
||||
}
|
||||
@@ -198,6 +238,27 @@ export default class SecretStorage extends EventEmitter {
|
||||
await this._baseApis.setAccountData(name, {encrypted});
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a secret defined to be the same as the given key.
|
||||
* No secret information will be stored, instead the secret will
|
||||
* be stored with a marker to say that the contents of the secret is
|
||||
* the value of the given key.
|
||||
* This is useful for migration from systems that predate SSSS such as
|
||||
* key backup.
|
||||
*
|
||||
* @param {string} name The name of the secret
|
||||
* @param {string} keyId The ID of the key whose value will be the
|
||||
* value of the secret
|
||||
* @returns {Promise} resolved when account data is saved
|
||||
*/
|
||||
storePassthrough(name, keyId) {
|
||||
return this._baseApis.setAccountData(name, {
|
||||
[keyId]: {
|
||||
passthrough: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a secret from storage.
|
||||
*
|
||||
@@ -206,25 +267,22 @@ export default class SecretStorage extends EventEmitter {
|
||||
* @return {string} the contents of the secret
|
||||
*/
|
||||
async get(name) {
|
||||
const secretInfo = this._baseApis.getAccountData(name);
|
||||
const secretInfo = await this._baseApis.getAccountDataFromServer(name);
|
||||
if (!secretInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
const secretContent = secretInfo.getContent();
|
||||
|
||||
if (!secretContent.encrypted) {
|
||||
if (!secretInfo.encrypted) {
|
||||
throw new Error("Content is not encrypted!");
|
||||
}
|
||||
|
||||
// get possible keys to decrypt
|
||||
const keys = {};
|
||||
for (const keyId of Object.keys(secretContent.encrypted)) {
|
||||
for (const keyId of Object.keys(secretInfo.encrypted)) {
|
||||
// get key information from key storage
|
||||
const keyInfo = this._baseApis.getAccountData(
|
||||
const keyInfo = await this._baseApis.getAccountDataFromServer(
|
||||
"m.secret_storage.key." + keyId,
|
||||
).getContent();
|
||||
const encInfo = secretContent.encrypted[keyId];
|
||||
);
|
||||
const encInfo = secretInfo.encrypted[keyId];
|
||||
switch (keyInfo.algorithm) {
|
||||
case SECRET_STORAGE_ALGORITHM_V1:
|
||||
if (keyInfo.pubkey && encInfo.ciphertext && encInfo.mac
|
||||
@@ -243,8 +301,13 @@ export default class SecretStorage extends EventEmitter {
|
||||
// fetch private key from app
|
||||
[keyId, decryption] = await this._getSecretStorageKey(keys);
|
||||
|
||||
const encInfo = secretInfo.encrypted[keyId];
|
||||
|
||||
// We don't actually need the decryption object if it's a passthrough
|
||||
// since we just want to return the key itself.
|
||||
if (encInfo.passthrough) return decryption.get_private_key();
|
||||
|
||||
// decrypt secret
|
||||
const encInfo = secretContent.encrypted[keyId];
|
||||
switch (keys[keyId].algorithm) {
|
||||
case SECRET_STORAGE_ALGORITHM_V1:
|
||||
return decryption.decrypt(
|
||||
@@ -264,29 +327,24 @@ export default class SecretStorage extends EventEmitter {
|
||||
*
|
||||
* @return {boolean} whether or not the secret is stored
|
||||
*/
|
||||
isStored(name, checkKey) {
|
||||
async isStored(name, checkKey) {
|
||||
// check if secret exists
|
||||
const secretInfo = this._baseApis.getAccountData(name);
|
||||
if (!secretInfo) {
|
||||
const secretInfo = await this._baseApis.getAccountDataFromServer(name);
|
||||
if (!secretInfo || !secretInfo.encrypted) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (checkKey === undefined) checkKey = true;
|
||||
|
||||
const secretContent = secretInfo.getContent();
|
||||
|
||||
if (!secretContent.encrypted) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if secret is encrypted by a known/trusted secret and
|
||||
// encryption looks sane
|
||||
for (const keyId of Object.keys(secretContent.encrypted)) {
|
||||
for (const keyId of Object.keys(secretInfo.encrypted)) {
|
||||
// get key information from key storage
|
||||
const keyInfo = this._baseApis.getAccountData(
|
||||
const keyInfo = await this._baseApis.getAccountDataFromServer(
|
||||
"m.secret_storage.key." + keyId,
|
||||
).getContent();
|
||||
const encInfo = secretContent.encrypted[keyId];
|
||||
);
|
||||
if (!keyInfo) return false;
|
||||
const encInfo = secretInfo.encrypted[keyId];
|
||||
if (checkKey) {
|
||||
pkVerify(
|
||||
keyInfo,
|
||||
@@ -487,9 +545,7 @@ export default class SecretStorage extends EventEmitter {
|
||||
throw new Error("No getSecretStorageKey callback supplied");
|
||||
}
|
||||
|
||||
const returned = await Promise.resolve(
|
||||
this._cryptoCallbacks.getSecretStorageKey({keys}),
|
||||
);
|
||||
const returned = await this._cryptoCallbacks.getSecretStorageKey({ keys });
|
||||
|
||||
if (!returned) {
|
||||
throw new Error("getSecretStorageKey callback returned falsey");
|
||||
|
||||
@@ -20,8 +20,6 @@ limitations under the License.
|
||||
* @module
|
||||
*/
|
||||
|
||||
import Promise from 'bluebird';
|
||||
|
||||
/**
|
||||
* map of registered encryption algorithm classes. A map from string to {@link
|
||||
* module:crypto/algorithms/base.EncryptionAlgorithm|EncryptionAlgorithm} class
|
||||
@@ -52,7 +50,7 @@ export const DECRYPTION_CLASSES = {};
|
||||
* @param {string} params.roomId The ID of the room we will be sending to
|
||||
* @param {object} params.config The body of the m.room.encryption event
|
||||
*/
|
||||
class EncryptionAlgorithm {
|
||||
export class EncryptionAlgorithm {
|
||||
constructor(params) {
|
||||
this._userId = params.userId;
|
||||
this._deviceId = params.deviceId;
|
||||
@@ -86,7 +84,6 @@ class EncryptionAlgorithm {
|
||||
onRoomMembership(event, member, oldMembership) {
|
||||
}
|
||||
}
|
||||
export {EncryptionAlgorithm}; // https://github.com/jsdoc3/jsdoc/issues/1272
|
||||
|
||||
/**
|
||||
* base type for decryption implementations
|
||||
@@ -100,7 +97,7 @@ export {EncryptionAlgorithm}; // https://github.com/jsdoc3/jsdoc/issues/1272
|
||||
* @param {string=} params.roomId The ID of the room we will be receiving
|
||||
* from. Null for to-device events.
|
||||
*/
|
||||
class DecryptionAlgorithm {
|
||||
export class DecryptionAlgorithm {
|
||||
constructor(params) {
|
||||
this._userId = params.userId;
|
||||
this._crypto = params.crypto;
|
||||
@@ -161,8 +158,17 @@ class DecryptionAlgorithm {
|
||||
shareKeysWithDevice(keyRequest) {
|
||||
throw new Error("shareKeysWithDevice not supported for this DecryptionAlgorithm");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry decrypting all the events from a sender that haven't been
|
||||
* decrypted yet.
|
||||
*
|
||||
* @param {string} senderKey the sender's key
|
||||
*/
|
||||
async retryDecryptionFromSender(senderKey) {
|
||||
// ignore by default
|
||||
}
|
||||
}
|
||||
export {DecryptionAlgorithm}; // https://github.com/jsdoc3/jsdoc/issues/1272
|
||||
|
||||
/**
|
||||
* Exception thrown when decryption fails
|
||||
@@ -175,7 +181,7 @@ export {DecryptionAlgorithm}; // https://github.com/jsdoc3/jsdoc/issues/1272
|
||||
*
|
||||
* @extends Error
|
||||
*/
|
||||
class DecryptionError extends Error {
|
||||
export class DecryptionError extends Error {
|
||||
constructor(code, msg, details) {
|
||||
super(msg);
|
||||
this.code = code;
|
||||
@@ -183,7 +189,6 @@ class DecryptionError extends Error {
|
||||
this.detailedString = _detailedStringForDecryptionError(this, details);
|
||||
}
|
||||
}
|
||||
export {DecryptionError}; // https://github.com/jsdoc3/jsdoc/issues/1272
|
||||
|
||||
function _detailedStringForDecryptionError(err, details) {
|
||||
let result = err.name + '[msg: ' + err.message;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
Copyright 2019 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.
|
||||
@@ -13,28 +14,12 @@ 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.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* @module crypto/algorithms
|
||||
*/
|
||||
|
||||
const base = require("./base");
|
||||
import "./olm";
|
||||
import "./megolm";
|
||||
|
||||
require("./olm");
|
||||
require("./megolm");
|
||||
|
||||
/**
|
||||
* @see module:crypto/algorithms/base.ENCRYPTION_CLASSES
|
||||
*/
|
||||
module.exports.ENCRYPTION_CLASSES = base.ENCRYPTION_CLASSES;
|
||||
|
||||
/**
|
||||
* @see module:crypto/algorithms/base.DECRYPTION_CLASSES
|
||||
*/
|
||||
module.exports.DECRYPTION_CLASSES = base.DECRYPTION_CLASSES;
|
||||
|
||||
/**
|
||||
* @see module:crypto/algorithms/base.DecryptionError
|
||||
*/
|
||||
module.exports.DecryptionError = base.DecryptionError;
|
||||
export * from "./base";
|
||||
|
||||
+460
-84
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2020 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.
|
||||
@@ -14,7 +15,6 @@ 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.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Defines m.olm encryption/decryption
|
||||
@@ -22,12 +22,19 @@ limitations under the License.
|
||||
* @module crypto/algorithms/megolm
|
||||
*/
|
||||
|
||||
import Promise from 'bluebird';
|
||||
import logger from '../../logger';
|
||||
import {logger} from '../../logger';
|
||||
import * as utils from "../../utils";
|
||||
import {polyfillSuper} from "../../utils";
|
||||
import * as olmlib from "../olmlib";
|
||||
import {
|
||||
DecryptionAlgorithm,
|
||||
DecryptionError,
|
||||
EncryptionAlgorithm,
|
||||
registerAlgorithm,
|
||||
UnknownDeviceError,
|
||||
} from "./base";
|
||||
|
||||
const utils = require("../../utils");
|
||||
const olmlib = require("../olmlib");
|
||||
const base = require("./base");
|
||||
import {WITHHELD_MESSAGES} from '../OlmDevice';
|
||||
|
||||
/**
|
||||
* @private
|
||||
@@ -48,6 +55,7 @@ function OutboundSessionInfo(sessionId) {
|
||||
this.useCount = 0;
|
||||
this.creationTime = new Date().getTime();
|
||||
this.sharedWithDevices = {};
|
||||
this.blockedDevicesNotified = {};
|
||||
}
|
||||
|
||||
|
||||
@@ -85,6 +93,15 @@ OutboundSessionInfo.prototype.markSharedWithDevice = function(
|
||||
this.sharedWithDevices[userId][deviceId] = chainIndex;
|
||||
};
|
||||
|
||||
OutboundSessionInfo.prototype.markNotifiedBlockedDevice = function(
|
||||
userId, deviceId,
|
||||
) {
|
||||
if (!this.blockedDevicesNotified[userId]) {
|
||||
this.blockedDevicesNotified[userId] = {};
|
||||
}
|
||||
this.blockedDevicesNotified[userId][deviceId] = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine if this session has been shared with devices which it shouldn't
|
||||
* have been.
|
||||
@@ -129,13 +146,13 @@ OutboundSessionInfo.prototype.sharedWithTooManyDevices = function(
|
||||
* Megolm encryption implementation
|
||||
*
|
||||
* @constructor
|
||||
* @extends {module:crypto/algorithms/base.EncryptionAlgorithm}
|
||||
* @extends {module:crypto/algorithms/EncryptionAlgorithm}
|
||||
*
|
||||
* @param {object} params parameters, as per
|
||||
* {@link module:crypto/algorithms/base.EncryptionAlgorithm}
|
||||
* {@link module:crypto/algorithms/EncryptionAlgorithm}
|
||||
*/
|
||||
function MegolmEncryption(params) {
|
||||
base.EncryptionAlgorithm.call(this, params);
|
||||
polyfillSuper(this, EncryptionAlgorithm, params);
|
||||
|
||||
// the most recent attempt to set up a session. This is used to serialise
|
||||
// the session setups, so that we have a race-free view of which session we
|
||||
@@ -161,17 +178,20 @@ function MegolmEncryption(params) {
|
||||
this._sessionRotationPeriodMsgs = params.config.rotation_period_msgs;
|
||||
}
|
||||
}
|
||||
utils.inherits(MegolmEncryption, base.EncryptionAlgorithm);
|
||||
utils.inherits(MegolmEncryption, EncryptionAlgorithm);
|
||||
|
||||
/**
|
||||
* @private
|
||||
*
|
||||
* @param {Object} devicesInRoom The devices in this room, indexed by user ID
|
||||
* @param {Object} blocked The devices that are blocked, indexed by user ID
|
||||
*
|
||||
* @return {module:client.Promise} Promise which resolves to the
|
||||
* OutboundSessionInfo when setup is complete.
|
||||
*/
|
||||
MegolmEncryption.prototype._ensureOutboundSession = function(devicesInRoom) {
|
||||
MegolmEncryption.prototype._ensureOutboundSession = async function(
|
||||
devicesInRoom, blocked,
|
||||
) {
|
||||
const self = this;
|
||||
|
||||
let session;
|
||||
@@ -238,9 +258,50 @@ MegolmEncryption.prototype._ensureOutboundSession = function(devicesInRoom) {
|
||||
}
|
||||
}
|
||||
|
||||
return self._shareKeyWithDevices(
|
||||
session, shareMap,
|
||||
const errorDevices = [];
|
||||
|
||||
await self._shareKeyWithDevices(
|
||||
session, shareMap, errorDevices,
|
||||
);
|
||||
|
||||
// are there any new blocked devices that we need to notify?
|
||||
const blockedMap = {};
|
||||
for (const userId in blocked) {
|
||||
if (!blocked.hasOwnProperty(userId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const userBlockedDevices = blocked[userId];
|
||||
|
||||
for (const deviceId in userBlockedDevices) {
|
||||
if (!userBlockedDevices.hasOwnProperty(deviceId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
!session.blockedDevicesNotified[userId] ||
|
||||
session.blockedDevicesNotified[userId][deviceId] === undefined
|
||||
) {
|
||||
blockedMap[userId] = blockedMap[userId] || [];
|
||||
blockedMap[userId].push(userBlockedDevices[deviceId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const filteredErrorDevices =
|
||||
await self._olmDevice.filterOutNotifiedErrorDevices(errorDevices);
|
||||
for (const {userId, deviceInfo} of filteredErrorDevices) {
|
||||
blockedMap[userId] = blockedMap[userId] || [];
|
||||
blockedMap[userId].push({
|
||||
code: "m.no_olm",
|
||||
reason: WITHHELD_MESSAGES["m.no_olm"],
|
||||
deviceInfo,
|
||||
});
|
||||
}
|
||||
|
||||
// notify blocked devices that they're blocked
|
||||
await self._notifyBlockedDevices(session, blockedMap);
|
||||
}
|
||||
|
||||
// helper which returns the session prepared by prepareSession
|
||||
@@ -288,6 +349,10 @@ MegolmEncryption.prototype._prepareNewSession = async function() {
|
||||
};
|
||||
|
||||
/**
|
||||
* Splits the user device map into multiple chunks to reduce the number of
|
||||
* devices we encrypt to per API call. Also filters out devices we don't have
|
||||
* a session with.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {module:crypto/algorithms/megolm.OutboundSessionInfo} session
|
||||
@@ -300,12 +365,16 @@ MegolmEncryption.prototype._prepareNewSession = async function() {
|
||||
* @param {object<string, module:crypto/deviceinfo[]>} devicesByUser
|
||||
* map from userid to list of devices
|
||||
*
|
||||
* @param {array<object>} errorDevices
|
||||
* array that will be populated with the devices that can't get an
|
||||
* olm session for
|
||||
*
|
||||
* @return {array<object<userid, deviceInfo>>}
|
||||
*/
|
||||
MegolmEncryption.prototype._splitUserDeviceMap = function(
|
||||
session, chainIndex, devicemap, devicesByUser,
|
||||
session, chainIndex, devicemap, devicesByUser, errorDevices,
|
||||
) {
|
||||
const maxToDeviceMessagesPerRequest = 20;
|
||||
const maxUsersPerRequest = 20;
|
||||
|
||||
// use an array where the slices of a content map gets stored
|
||||
const mapSlices = [];
|
||||
@@ -335,6 +404,8 @@ MegolmEncryption.prototype._splitUserDeviceMap = function(
|
||||
// to claim a one-time-key for dead devices on every message.
|
||||
session.markSharedWithDevice(userId, deviceId, chainIndex);
|
||||
|
||||
errorDevices.push({userId, deviceInfo});
|
||||
|
||||
// ensureOlmSessionsForUsers has already done the logging,
|
||||
// so just skip it.
|
||||
continue;
|
||||
@@ -344,11 +415,6 @@ MegolmEncryption.prototype._splitUserDeviceMap = function(
|
||||
"share keys with device " + userId + ":" + deviceId,
|
||||
);
|
||||
|
||||
if (entriesInCurrentSlice > maxToDeviceMessagesPerRequest) {
|
||||
// the current slice is filled up. Start inserting into the next slice
|
||||
entriesInCurrentSlice = 0;
|
||||
currentSliceId++;
|
||||
}
|
||||
if (!mapSlices[currentSliceId]) {
|
||||
mapSlices[currentSliceId] = [];
|
||||
}
|
||||
@@ -360,6 +426,61 @@ MegolmEncryption.prototype._splitUserDeviceMap = function(
|
||||
|
||||
entriesInCurrentSlice++;
|
||||
}
|
||||
|
||||
// We do this in the per-user loop as we prefer that all messages to the
|
||||
// same user end up in the same API call to make it easier for the
|
||||
// server (e.g. only have to send one EDU if a remote user, etc). This
|
||||
// does mean that if a user has many devices we may go over the desired
|
||||
// limit, but its not a hard limit so that is fine.
|
||||
if (entriesInCurrentSlice > maxUsersPerRequest) {
|
||||
// the current slice is filled up. Start inserting into the next slice
|
||||
entriesInCurrentSlice = 0;
|
||||
currentSliceId++;
|
||||
}
|
||||
}
|
||||
return mapSlices;
|
||||
};
|
||||
|
||||
/**
|
||||
* Splits the user device map into multiple chunks to reduce the number of
|
||||
* devices we encrypt to per API call.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {object} devicesByUser map from userid to list of devices
|
||||
*
|
||||
* @return {array<array<object>>} the blocked devices, split into chunks
|
||||
*/
|
||||
MegolmEncryption.prototype._splitBlockedDevices = function(devicesByUser) {
|
||||
const maxUsersPerRequest = 20;
|
||||
|
||||
// use an array where the slices of a content map gets stored
|
||||
let currentSlice = [];
|
||||
const mapSlices = [currentSlice];
|
||||
|
||||
for (const userId of Object.keys(devicesByUser)) {
|
||||
const userBlockedDevicesToShareWith = devicesByUser[userId];
|
||||
|
||||
for (const blockedInfo of userBlockedDevicesToShareWith) {
|
||||
currentSlice.push({
|
||||
userId: userId,
|
||||
blockedInfo: blockedInfo,
|
||||
});
|
||||
}
|
||||
|
||||
// We do this in the per-user loop as we prefer that all messages to the
|
||||
// same user end up in the same API call to make it easier for the
|
||||
// server (e.g. only have to send one EDU if a remote user, etc). This
|
||||
// does mean that if a user has many devices we may go over the desired
|
||||
// limit, but its not a hard limit so that is fine.
|
||||
if (currentSlice.length > maxUsersPerRequest) {
|
||||
// the current slice is filled up. Start inserting into the next slice
|
||||
currentSlice = [];
|
||||
mapSlices.push(currentSlice);
|
||||
}
|
||||
}
|
||||
if (currentSlice.length === 0) {
|
||||
mapSlices.pop();
|
||||
}
|
||||
return mapSlices;
|
||||
};
|
||||
@@ -382,15 +503,15 @@ MegolmEncryption.prototype._splitUserDeviceMap = function(
|
||||
MegolmEncryption.prototype._encryptAndSendKeysToDevices = function(
|
||||
session, chainIndex, userDeviceMap, payload,
|
||||
) {
|
||||
const encryptedContent = {
|
||||
algorithm: olmlib.OLM_ALGORITHM,
|
||||
sender_key: this._olmDevice.deviceCurve25519Key,
|
||||
ciphertext: {},
|
||||
};
|
||||
const contentMap = {};
|
||||
|
||||
const promises = [];
|
||||
for (let i = 0; i < userDeviceMap.length; i++) {
|
||||
const encryptedContent = {
|
||||
algorithm: olmlib.OLM_ALGORITHM,
|
||||
sender_key: this._olmDevice.deviceCurve25519Key,
|
||||
ciphertext: {},
|
||||
};
|
||||
const val = userDeviceMap[i];
|
||||
const userId = val.userId;
|
||||
const deviceInfo = val.deviceInfo;
|
||||
@@ -428,6 +549,53 @@ MegolmEncryption.prototype._encryptAndSendKeysToDevices = function(
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*
|
||||
* @param {module:crypto/algorithms/megolm.OutboundSessionInfo} session
|
||||
*
|
||||
* @param {array<object>} userDeviceMap list of blocked devices to notify
|
||||
*
|
||||
* @param {object} payload fields to include in the notification payload
|
||||
*
|
||||
* @return {module:client.Promise} Promise which resolves once the notifications
|
||||
* for the given userDeviceMap is generated and has been sent.
|
||||
*/
|
||||
MegolmEncryption.prototype._sendBlockedNotificationsToDevices = async function(
|
||||
session, userDeviceMap, payload,
|
||||
) {
|
||||
const contentMap = {};
|
||||
|
||||
for (const val of userDeviceMap) {
|
||||
const userId = val.userId;
|
||||
const blockedInfo = val.blockedInfo;
|
||||
const deviceInfo = blockedInfo.deviceInfo;
|
||||
const deviceId = deviceInfo.deviceId;
|
||||
|
||||
const message = Object.assign({}, payload);
|
||||
message.code = blockedInfo.code;
|
||||
message.reason = blockedInfo.reason;
|
||||
if (message.code === "m.no_olm") {
|
||||
delete message.room_id;
|
||||
delete message.session_id;
|
||||
}
|
||||
|
||||
if (!contentMap[userId]) {
|
||||
contentMap[userId] = {};
|
||||
}
|
||||
contentMap[userId][deviceId] = message;
|
||||
}
|
||||
|
||||
await this._baseApis.sendToDevice("org.matrix.room_key.withheld", contentMap);
|
||||
|
||||
// store that we successfully uploaded the keys of the current slice
|
||||
for (const userId of Object.keys(contentMap)) {
|
||||
for (const deviceId of Object.keys(contentMap[userId])) {
|
||||
session.markNotifiedBlockedDevice(userId, deviceId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Re-shares a megolm session key with devices if the key has already been
|
||||
* sent to them.
|
||||
@@ -508,7 +676,7 @@ MegolmEncryption.prototype.reshareKeyWithDevice = async function(
|
||||
userId,
|
||||
device,
|
||||
payload,
|
||||
),
|
||||
);
|
||||
|
||||
await this._baseApis.sendToDevice("m.room.encrypted", {
|
||||
[userId]: {
|
||||
@@ -524,8 +692,14 @@ MegolmEncryption.prototype.reshareKeyWithDevice = async function(
|
||||
*
|
||||
* @param {object<string, module:crypto/deviceinfo[]>} devicesByUser
|
||||
* map from userid to list of devices
|
||||
*
|
||||
* @param {array<object>} errorDevices
|
||||
* array that will be populated with the devices that we can't get an
|
||||
* olm session for
|
||||
*/
|
||||
MegolmEncryption.prototype._shareKeyWithDevices = async function(session, devicesByUser) {
|
||||
MegolmEncryption.prototype._shareKeyWithDevices = async function(
|
||||
session, devicesByUser, errorDevices,
|
||||
) {
|
||||
const key = this._olmDevice.getOutboundGroupSessionKey(session.sessionId);
|
||||
const payload = {
|
||||
type: "m.room_key",
|
||||
@@ -543,7 +717,7 @@ MegolmEncryption.prototype._shareKeyWithDevices = async function(session, device
|
||||
);
|
||||
|
||||
const userDeviceMaps = this._splitUserDeviceMap(
|
||||
session, key.chain_index, devicemap, devicesByUser,
|
||||
session, key.chain_index, devicemap, devicesByUser, errorDevices,
|
||||
);
|
||||
|
||||
for (let i = 0; i < userDeviceMaps.length; i++) {
|
||||
@@ -562,6 +736,42 @@ MegolmEncryption.prototype._shareKeyWithDevices = async function(session, device
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Notify blocked devices that they have been blocked.
|
||||
*
|
||||
* @param {module:crypto/algorithms/megolm.OutboundSessionInfo} session
|
||||
*
|
||||
* @param {object<string, object>} devicesByUser
|
||||
* map from userid to device ID to blocked data
|
||||
*/
|
||||
MegolmEncryption.prototype._notifyBlockedDevices = async function(
|
||||
session, devicesByUser,
|
||||
) {
|
||||
const payload = {
|
||||
room_id: this._roomId,
|
||||
session_id: session.sessionId,
|
||||
algorithm: olmlib.MEGOLM_ALGORITHM,
|
||||
sender_key: this._olmDevice.deviceCurve25519Key,
|
||||
};
|
||||
|
||||
const userDeviceMaps = this._splitBlockedDevices(devicesByUser);
|
||||
|
||||
for (let i = 0; i < userDeviceMaps.length; i++) {
|
||||
try {
|
||||
await this._sendBlockedNotificationsToDevices(
|
||||
session, userDeviceMaps[i], payload,
|
||||
);
|
||||
logger.log(`Completed blacklist notification for ${session.sessionId} `
|
||||
+ `in ${this._roomId} (slice ${i + 1}/${userDeviceMaps.length})`);
|
||||
} catch (e) {
|
||||
logger.log(`blacklist notification for ${session.sessionId} in `
|
||||
+ `${this._roomId} (slice ${i + 1}/${userDeviceMaps.length}) failed`);
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*
|
||||
@@ -571,42 +781,43 @@ MegolmEncryption.prototype._shareKeyWithDevices = async function(session, device
|
||||
*
|
||||
* @return {module:client.Promise} Promise which resolves to the new event body
|
||||
*/
|
||||
MegolmEncryption.prototype.encryptMessage = function(room, eventType, content) {
|
||||
MegolmEncryption.prototype.encryptMessage = async function(room, eventType, content) {
|
||||
const self = this;
|
||||
logger.log(`Starting to encrypt event for ${this._roomId}`);
|
||||
|
||||
return this._getDevicesInRoom(room).then(function(devicesInRoom) {
|
||||
// check if any of these devices are not yet known to the user.
|
||||
// if so, warn the user so they can verify or ignore.
|
||||
const [devicesInRoom, blocked] = await this._getDevicesInRoom(room);
|
||||
|
||||
// check if any of these devices are not yet known to the user.
|
||||
// if so, warn the user so they can verify or ignore.
|
||||
if (this._crypto.getGlobalErrorOnUnknownDevices()) {
|
||||
self._checkForUnknownDevices(devicesInRoom);
|
||||
}
|
||||
|
||||
return self._ensureOutboundSession(devicesInRoom);
|
||||
}).then(function(session) {
|
||||
const payloadJson = {
|
||||
room_id: self._roomId,
|
||||
type: eventType,
|
||||
content: content,
|
||||
};
|
||||
const session = await self._ensureOutboundSession(devicesInRoom, blocked);
|
||||
const payloadJson = {
|
||||
room_id: self._roomId,
|
||||
type: eventType,
|
||||
content: content,
|
||||
};
|
||||
|
||||
const ciphertext = self._olmDevice.encryptGroupMessage(
|
||||
session.sessionId, JSON.stringify(payloadJson),
|
||||
);
|
||||
const ciphertext = self._olmDevice.encryptGroupMessage(
|
||||
session.sessionId, JSON.stringify(payloadJson),
|
||||
);
|
||||
|
||||
const encryptedContent = {
|
||||
algorithm: olmlib.MEGOLM_ALGORITHM,
|
||||
sender_key: self._olmDevice.deviceCurve25519Key,
|
||||
ciphertext: ciphertext,
|
||||
session_id: session.sessionId,
|
||||
// Include our device ID so that recipients can send us a
|
||||
// m.new_device message if they don't have our session key.
|
||||
// XXX: Do we still need this now that m.new_device messages
|
||||
// no longer exist since #483?
|
||||
device_id: self._deviceId,
|
||||
};
|
||||
const encryptedContent = {
|
||||
algorithm: olmlib.MEGOLM_ALGORITHM,
|
||||
sender_key: self._olmDevice.deviceCurve25519Key,
|
||||
ciphertext: ciphertext,
|
||||
session_id: session.sessionId,
|
||||
// Include our device ID so that recipients can send us a
|
||||
// m.new_device message if they don't have our session key.
|
||||
// XXX: Do we still need this now that m.new_device messages
|
||||
// no longer exist since #483?
|
||||
device_id: self._deviceId,
|
||||
};
|
||||
|
||||
session.useCount++;
|
||||
return encryptedContent;
|
||||
});
|
||||
session.useCount++;
|
||||
return encryptedContent;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -644,7 +855,7 @@ MegolmEncryption.prototype._checkForUnknownDevices = function(devicesInRoom) {
|
||||
|
||||
if (Object.keys(unknownDevices).length) {
|
||||
// it'd be kind to pass unknownDevices up to the user in this error
|
||||
throw new base.UnknownDeviceError(
|
||||
throw new UnknownDeviceError(
|
||||
"This room contains unknown devices which have not been verified. " +
|
||||
"We strongly recommend you verify them before continuing.", unknownDevices);
|
||||
}
|
||||
@@ -655,8 +866,11 @@ MegolmEncryption.prototype._checkForUnknownDevices = function(devicesInRoom) {
|
||||
*
|
||||
* @param {module:models/room} room
|
||||
*
|
||||
* @return {module:client.Promise} Promise which resolves to a map
|
||||
* from userId to deviceId to deviceInfo
|
||||
* @return {module:client.Promise} Promise which resolves to an array whose
|
||||
* first element is a map from userId to deviceId to deviceInfo indicating
|
||||
* the devices that messages should be encrypted to, and whose second
|
||||
* element is a map from userId to deviceId to data indicating the devices
|
||||
* that are in the room but that have been blocked
|
||||
*/
|
||||
MegolmEncryption.prototype._getDevicesInRoom = async function(room) {
|
||||
const members = await room.getEncryptionTargetMembers();
|
||||
@@ -677,6 +891,7 @@ MegolmEncryption.prototype._getDevicesInRoom = async function(room) {
|
||||
// using all the device_lists changes and left fields.
|
||||
// See https://github.com/vector-im/riot-web/issues/2305 for details.
|
||||
const devices = await this._crypto.downloadKeys(roomMembers, false);
|
||||
const blocked = {};
|
||||
// remove any blocked devices
|
||||
for (const userId in devices) {
|
||||
if (!devices.hasOwnProperty(userId)) {
|
||||
@@ -691,26 +906,40 @@ MegolmEncryption.prototype._getDevicesInRoom = async function(room) {
|
||||
|
||||
if (userDevices[deviceId].isBlocked() ||
|
||||
(userDevices[deviceId].isUnverified() && isBlacklisting)
|
||||
) {
|
||||
) {
|
||||
if (!blocked[userId]) {
|
||||
blocked[userId] = {};
|
||||
}
|
||||
const blockedInfo = userDevices[deviceId].isBlocked()
|
||||
? {
|
||||
code: "m.blacklisted",
|
||||
reason: WITHHELD_MESSAGES["m.blacklisted"],
|
||||
}
|
||||
: {
|
||||
code: "m.unverified",
|
||||
reason: WITHHELD_MESSAGES["m.unverified"],
|
||||
};
|
||||
blockedInfo.deviceInfo = userDevices[deviceId];
|
||||
blocked[userId][deviceId] = blockedInfo;
|
||||
delete userDevices[deviceId];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return devices;
|
||||
return [devices, blocked];
|
||||
};
|
||||
|
||||
/**
|
||||
* Megolm decryption implementation
|
||||
*
|
||||
* @constructor
|
||||
* @extends {module:crypto/algorithms/base.DecryptionAlgorithm}
|
||||
* @extends {module:crypto/algorithms/DecryptionAlgorithm}
|
||||
*
|
||||
* @param {object} params parameters, as per
|
||||
* {@link module:crypto/algorithms/base.DecryptionAlgorithm}
|
||||
* {@link module:crypto/algorithms/DecryptionAlgorithm}
|
||||
*/
|
||||
function MegolmDecryption(params) {
|
||||
base.DecryptionAlgorithm.call(this, params);
|
||||
polyfillSuper(this, DecryptionAlgorithm, params);
|
||||
|
||||
// events which we couldn't decrypt due to unknown sessions / indexes: map from
|
||||
// senderKey|sessionId to Set of MatrixEvents
|
||||
@@ -719,7 +948,12 @@ function MegolmDecryption(params) {
|
||||
// this gets stubbed out by the unit tests.
|
||||
this.olmlib = olmlib;
|
||||
}
|
||||
utils.inherits(MegolmDecryption, base.DecryptionAlgorithm);
|
||||
utils.inherits(MegolmDecryption, DecryptionAlgorithm);
|
||||
|
||||
const PROBLEM_DESCRIPTIONS = {
|
||||
no_olm: "The sender was unable to establish a secure channel.",
|
||||
unknown: "The secure channel with the sender was corrupted.",
|
||||
};
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
@@ -737,7 +971,7 @@ MegolmDecryption.prototype.decryptEvent = async function(event) {
|
||||
if (!content.sender_key || !content.session_id ||
|
||||
!content.ciphertext
|
||||
) {
|
||||
throw new base.DecryptionError(
|
||||
throw new DecryptionError(
|
||||
"MEGOLM_MISSING_FIELDS",
|
||||
"Missing fields in input",
|
||||
);
|
||||
@@ -757,6 +991,11 @@ MegolmDecryption.prototype.decryptEvent = async function(event) {
|
||||
event.getId(), event.getTs(),
|
||||
);
|
||||
} catch (e) {
|
||||
if (e.name === "DecryptionError") {
|
||||
// re-throw decryption errors as-is
|
||||
throw e;
|
||||
}
|
||||
|
||||
let errorCode = "OLM_DECRYPT_GROUP_MESSAGE_ERROR";
|
||||
|
||||
if (e && e.message === 'OLM.UNKNOWN_MESSAGE_INDEX') {
|
||||
@@ -765,7 +1004,7 @@ MegolmDecryption.prototype.decryptEvent = async function(event) {
|
||||
errorCode = 'OLM_UNKNOWN_MESSAGE_INDEX';
|
||||
}
|
||||
|
||||
throw new base.DecryptionError(
|
||||
throw new DecryptionError(
|
||||
errorCode,
|
||||
e ? e.toString() : "Unknown Error: Error is undefined", {
|
||||
session: content.sender_key + '|' + content.session_id,
|
||||
@@ -782,7 +1021,29 @@ MegolmDecryption.prototype.decryptEvent = async function(event) {
|
||||
// event is still in the pending list; if not, a retry will have been
|
||||
// scheduled, so we needn't send out the request here.)
|
||||
this._requestKeysForEvent(event);
|
||||
throw new base.DecryptionError(
|
||||
|
||||
// See if there was a problem with the olm session at the time the
|
||||
// event was sent. Use a fuzz factor of 2 minutes.
|
||||
const problem = await this._olmDevice.sessionMayHaveProblems(
|
||||
content.sender_key, event.getTs() - 120000,
|
||||
);
|
||||
if (problem) {
|
||||
let problemDescription = PROBLEM_DESCRIPTIONS[problem.type]
|
||||
|| PROBLEM_DESCRIPTIONS.unknown;
|
||||
if (problem.fixed) {
|
||||
problemDescription +=
|
||||
" Trying to create a new secure channel and re-requesting the keys.";
|
||||
}
|
||||
throw new DecryptionError(
|
||||
"MEGOLM_UNKNOWN_INBOUND_SESSION_ID",
|
||||
problemDescription,
|
||||
{
|
||||
session: content.sender_key + '|' + content.session_id,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
throw new DecryptionError(
|
||||
"MEGOLM_UNKNOWN_INBOUND_SESSION_ID",
|
||||
"The sender's device has not sent us the keys for this message.",
|
||||
{
|
||||
@@ -801,7 +1062,7 @@ MegolmDecryption.prototype.decryptEvent = async function(event) {
|
||||
// (this is somewhat redundant, since the megolm session is scoped to the
|
||||
// room, so neither the sender nor a MITM can lie about the room_id).
|
||||
if (payload.room_id !== event.getRoomId()) {
|
||||
throw new base.DecryptionError(
|
||||
throw new DecryptionError(
|
||||
"MEGOLM_BAD_ROOM",
|
||||
"Message intended for room " + payload.room_id,
|
||||
);
|
||||
@@ -837,11 +1098,16 @@ MegolmDecryption.prototype._requestKeysForEvent = function(event) {
|
||||
*/
|
||||
MegolmDecryption.prototype._addEventToPendingList = function(event) {
|
||||
const content = event.getWireContent();
|
||||
const k = content.sender_key + "|" + content.session_id;
|
||||
if (!this._pendingEvents[k]) {
|
||||
this._pendingEvents[k] = new Set();
|
||||
const senderKey = content.sender_key;
|
||||
const sessionId = content.session_id;
|
||||
if (!this._pendingEvents[senderKey]) {
|
||||
this._pendingEvents[senderKey] = new Map();
|
||||
}
|
||||
this._pendingEvents[k].add(event);
|
||||
const senderPendingEvents = this._pendingEvents[senderKey];
|
||||
if (!senderPendingEvents.has(sessionId)) {
|
||||
senderPendingEvents.set(sessionId, new Set());
|
||||
}
|
||||
senderPendingEvents.get(sessionId).add(event);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -853,14 +1119,20 @@ MegolmDecryption.prototype._addEventToPendingList = function(event) {
|
||||
*/
|
||||
MegolmDecryption.prototype._removeEventFromPendingList = function(event) {
|
||||
const content = event.getWireContent();
|
||||
const k = content.sender_key + "|" + content.session_id;
|
||||
if (!this._pendingEvents[k]) {
|
||||
const senderKey = content.sender_key;
|
||||
const sessionId = content.session_id;
|
||||
const senderPendingEvents = this._pendingEvents[senderKey];
|
||||
const pendingEvents = senderPendingEvents && senderPendingEvents.get(sessionId);
|
||||
if (!pendingEvents) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._pendingEvents[k].delete(event);
|
||||
if (this._pendingEvents[k].size === 0) {
|
||||
delete this._pendingEvents[k];
|
||||
pendingEvents.delete(event);
|
||||
if (pendingEvents.size === 0) {
|
||||
senderPendingEvents.delete(senderKey);
|
||||
}
|
||||
if (senderPendingEvents.size === 0) {
|
||||
delete this._pendingEvents[senderKey];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -964,6 +1236,78 @@ MegolmDecryption.prototype.onRoomKeyEvent = function(event) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*
|
||||
* @param {module:models/event.MatrixEvent} event key event
|
||||
*/
|
||||
MegolmDecryption.prototype.onRoomKeyWithheldEvent = async function(event) {
|
||||
const content = event.getContent();
|
||||
const senderKey = content.sender_key;
|
||||
|
||||
if (content.code === "m.no_olm") {
|
||||
const sender = event.getSender();
|
||||
// if the sender says that they haven't been able to establish an olm
|
||||
// session, let's proactively establish one
|
||||
|
||||
// Note: after we record that the olm session has had a problem, we
|
||||
// trigger retrying decryption for all the messages from the sender's
|
||||
// key, so that we can update the error message to indicate the olm
|
||||
// session problem.
|
||||
|
||||
if (await this._olmDevice.getSessionIdForDevice(senderKey)) {
|
||||
// a session has already been established, so we don't need to
|
||||
// create a new one.
|
||||
await this._olmDevice.recordSessionProblem(senderKey, "no_olm", true);
|
||||
this.retryDecryptionFromSender(senderKey);
|
||||
return;
|
||||
}
|
||||
const device = this._crypto._deviceList.getDeviceByIdentityKey(
|
||||
content.algorithm, senderKey,
|
||||
);
|
||||
if (!device) {
|
||||
logger.info(
|
||||
"Couldn't find device for identity key " + senderKey +
|
||||
": not establishing session",
|
||||
);
|
||||
await this._olmDevice.recordSessionProblem(senderKey, "no_olm", false);
|
||||
this.retryDecryptionFromSender(senderKey);
|
||||
return;
|
||||
}
|
||||
await olmlib.ensureOlmSessionsForDevices(
|
||||
this._olmDevice, this._baseApis, {[sender]: [device]}, false,
|
||||
);
|
||||
const encryptedContent = {
|
||||
algorithm: olmlib.OLM_ALGORITHM,
|
||||
sender_key: this._olmDevice.deviceCurve25519Key,
|
||||
ciphertext: {},
|
||||
};
|
||||
await olmlib.encryptMessageForDevice(
|
||||
encryptedContent.ciphertext,
|
||||
this._userId,
|
||||
this._deviceId,
|
||||
this._olmDevice,
|
||||
sender,
|
||||
device,
|
||||
{type: "m.dummy"},
|
||||
);
|
||||
|
||||
await this._olmDevice.recordSessionProblem(senderKey, "no_olm", true);
|
||||
this.retryDecryptionFromSender(senderKey);
|
||||
|
||||
await this._baseApis.sendToDevice("m.room.encrypted", {
|
||||
[sender]: {
|
||||
[device.deviceId]: encryptedContent,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await this._olmDevice.addInboundGroupSessionWithheld(
|
||||
content.room_id, senderKey, content.session_id, content.code,
|
||||
content.reason,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
@@ -1036,7 +1380,7 @@ MegolmDecryption.prototype.shareKeysWithDevice = function(keyRequest) {
|
||||
// TODO: retries
|
||||
return this._baseApis.sendToDevice("m.room.encrypted", contentMap);
|
||||
});
|
||||
}).done();
|
||||
});
|
||||
};
|
||||
|
||||
MegolmDecryption.prototype._buildKeyForwardingMessage = async function(
|
||||
@@ -1107,13 +1451,20 @@ MegolmDecryption.prototype.importRoomKey = function(session) {
|
||||
* @return {Boolean} whether all messages were successfully decrypted
|
||||
*/
|
||||
MegolmDecryption.prototype._retryDecryption = async function(senderKey, sessionId) {
|
||||
const k = senderKey + "|" + sessionId;
|
||||
const pending = this._pendingEvents[k];
|
||||
const senderPendingEvents = this._pendingEvents[senderKey];
|
||||
if (!senderPendingEvents) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const pending = senderPendingEvents.get(sessionId);
|
||||
if (!pending) {
|
||||
return true;
|
||||
}
|
||||
|
||||
delete this._pendingEvents[k];
|
||||
pending.delete(sessionId);
|
||||
if (pending.size === 0) {
|
||||
this._pendingEvents[senderKey];
|
||||
}
|
||||
|
||||
await Promise.all([...pending].map(async (ev) => {
|
||||
try {
|
||||
@@ -1123,9 +1474,34 @@ MegolmDecryption.prototype._retryDecryption = async function(senderKey, sessionI
|
||||
}
|
||||
}));
|
||||
|
||||
return !this._pendingEvents[k];
|
||||
// ev.attemptDecryption will re-add to this._pendingEvents if an event
|
||||
// couldn't be decrypted
|
||||
return !((this._pendingEvents[senderKey] || {})[sessionId]);
|
||||
};
|
||||
|
||||
base.registerAlgorithm(
|
||||
MegolmDecryption.prototype.retryDecryptionFromSender = async function(senderKey) {
|
||||
const senderPendingEvents = this._pendingEvents[senderKey];
|
||||
logger.warn(senderPendingEvents);
|
||||
if (!senderPendingEvents) {
|
||||
return true;
|
||||
}
|
||||
|
||||
delete this._pendingEvents[senderKey];
|
||||
|
||||
await Promise.all([...senderPendingEvents].map(async ([_sessionId, pending]) => {
|
||||
await Promise.all([...pending].map(async (ev) => {
|
||||
try {
|
||||
logger.warn(ev.getId());
|
||||
await ev.attemptDecryption(this._crypto);
|
||||
} catch (e) {
|
||||
// don't die if something goes wrong
|
||||
}
|
||||
}));
|
||||
}));
|
||||
|
||||
return !this._pendingEvents[senderKey];
|
||||
};
|
||||
|
||||
registerAlgorithm(
|
||||
olmlib.MEGOLM_ALGORITHM, MegolmEncryption, MegolmDecryption,
|
||||
);
|
||||
|
||||
@@ -13,39 +13,42 @@ 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.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Defines m.olm encryption/decryption
|
||||
*
|
||||
* @module crypto/algorithms/olm
|
||||
*/
|
||||
import Promise from 'bluebird';
|
||||
|
||||
import logger from '../../logger';
|
||||
const utils = require("../../utils");
|
||||
const olmlib = require("../olmlib");
|
||||
const DeviceInfo = require("../deviceinfo");
|
||||
import {logger} from '../../logger';
|
||||
import * as utils from "../../utils";
|
||||
import {polyfillSuper} from "../../utils";
|
||||
import * as olmlib from "../olmlib";
|
||||
import {DeviceInfo} from "../deviceinfo";
|
||||
import {
|
||||
DecryptionAlgorithm,
|
||||
DecryptionError,
|
||||
EncryptionAlgorithm,
|
||||
registerAlgorithm,
|
||||
} from "./base";
|
||||
|
||||
const DeviceVerification = DeviceInfo.DeviceVerification;
|
||||
|
||||
|
||||
const base = require("./base");
|
||||
|
||||
/**
|
||||
* Olm encryption implementation
|
||||
*
|
||||
* @constructor
|
||||
* @extends {module:crypto/algorithms/base.EncryptionAlgorithm}
|
||||
* @extends {module:crypto/algorithms/EncryptionAlgorithm}
|
||||
*
|
||||
* @param {object} params parameters, as per
|
||||
* {@link module:crypto/algorithms/base.EncryptionAlgorithm}
|
||||
* {@link module:crypto/algorithms/EncryptionAlgorithm}
|
||||
*/
|
||||
function OlmEncryption(params) {
|
||||
base.EncryptionAlgorithm.call(this, params);
|
||||
polyfillSuper(this, EncryptionAlgorithm, params);
|
||||
this._sessionPrepared = false;
|
||||
this._prepPromise = null;
|
||||
}
|
||||
utils.inherits(OlmEncryption, base.EncryptionAlgorithm);
|
||||
utils.inherits(OlmEncryption, EncryptionAlgorithm);
|
||||
|
||||
/**
|
||||
* @private
|
||||
@@ -146,14 +149,14 @@ OlmEncryption.prototype.encryptMessage = async function(room, eventType, content
|
||||
* Olm decryption implementation
|
||||
*
|
||||
* @constructor
|
||||
* @extends {module:crypto/algorithms/base.DecryptionAlgorithm}
|
||||
* @extends {module:crypto/algorithms/DecryptionAlgorithm}
|
||||
* @param {object} params parameters, as per
|
||||
* {@link module:crypto/algorithms/base.DecryptionAlgorithm}
|
||||
* {@link module:crypto/algorithms/DecryptionAlgorithm}
|
||||
*/
|
||||
function OlmDecryption(params) {
|
||||
base.DecryptionAlgorithm.call(this, params);
|
||||
polyfillSuper(this, DecryptionAlgorithm, params);
|
||||
}
|
||||
utils.inherits(OlmDecryption, base.DecryptionAlgorithm);
|
||||
utils.inherits(OlmDecryption, DecryptionAlgorithm);
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
@@ -171,14 +174,14 @@ OlmDecryption.prototype.decryptEvent = async function(event) {
|
||||
const ciphertext = content.ciphertext;
|
||||
|
||||
if (!ciphertext) {
|
||||
throw new base.DecryptionError(
|
||||
throw new DecryptionError(
|
||||
"OLM_MISSING_CIPHERTEXT",
|
||||
"Missing ciphertext",
|
||||
);
|
||||
}
|
||||
|
||||
if (!(this._olmDevice.deviceCurve25519Key in ciphertext)) {
|
||||
throw new base.DecryptionError(
|
||||
throw new DecryptionError(
|
||||
"OLM_NOT_INCLUDED_IN_RECIPIENTS",
|
||||
"Not included in recipients",
|
||||
);
|
||||
@@ -189,7 +192,7 @@ OlmDecryption.prototype.decryptEvent = async function(event) {
|
||||
try {
|
||||
payloadString = await this._decryptMessage(deviceKey, message);
|
||||
} catch (e) {
|
||||
throw new base.DecryptionError(
|
||||
throw new DecryptionError(
|
||||
"OLM_BAD_ENCRYPTED_MESSAGE",
|
||||
"Bad Encrypted Message", {
|
||||
sender: deviceKey,
|
||||
@@ -203,14 +206,14 @@ OlmDecryption.prototype.decryptEvent = async function(event) {
|
||||
// check that we were the intended recipient, to avoid unknown-key attack
|
||||
// https://github.com/vector-im/vector-web/issues/2483
|
||||
if (payload.recipient != this._userId) {
|
||||
throw new base.DecryptionError(
|
||||
throw new DecryptionError(
|
||||
"OLM_BAD_RECIPIENT",
|
||||
"Message was intented for " + payload.recipient,
|
||||
);
|
||||
}
|
||||
|
||||
if (payload.recipient_keys.ed25519 != this._olmDevice.deviceEd25519Key) {
|
||||
throw new base.DecryptionError(
|
||||
throw new DecryptionError(
|
||||
"OLM_BAD_RECIPIENT_KEY",
|
||||
"Message not intended for this device", {
|
||||
intended: payload.recipient_keys.ed25519,
|
||||
@@ -224,7 +227,7 @@ OlmDecryption.prototype.decryptEvent = async function(event) {
|
||||
// (this check is also provided via the sender's embedded ed25519 key,
|
||||
// which is checked elsewhere).
|
||||
if (payload.sender != event.getSender()) {
|
||||
throw new base.DecryptionError(
|
||||
throw new DecryptionError(
|
||||
"OLM_FORWARDED_MESSAGE",
|
||||
"Message forwarded from " + payload.sender, {
|
||||
reported_sender: event.getSender(),
|
||||
@@ -234,7 +237,7 @@ OlmDecryption.prototype.decryptEvent = async function(event) {
|
||||
|
||||
// Olm events intended for a room have a room_id.
|
||||
if (payload.room_id !== event.getRoomId()) {
|
||||
throw new base.DecryptionError(
|
||||
throw new DecryptionError(
|
||||
"OLM_BAD_ROOM",
|
||||
"Message intended for room " + payload.room_id, {
|
||||
reported_room: event.room_id,
|
||||
@@ -337,4 +340,4 @@ OlmDecryption.prototype._decryptMessage = async function(
|
||||
};
|
||||
|
||||
|
||||
base.registerAlgorithm(olmlib.OLM_ALGORITHM, OlmEncryption, OlmDecryption);
|
||||
registerAlgorithm(olmlib.OLM_ALGORITHM, OlmEncryption, OlmDecryption);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
Copyright 2019 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.
|
||||
@@ -13,8 +14,6 @@ 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.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
|
||||
/**
|
||||
* @module crypto/deviceinfo
|
||||
@@ -44,7 +43,7 @@ limitations under the License.
|
||||
*
|
||||
* @param {string} deviceId id of the device
|
||||
*/
|
||||
function DeviceInfo(deviceId) {
|
||||
export function DeviceInfo(deviceId) {
|
||||
// you can't change the deviceId
|
||||
Object.defineProperty(this, 'deviceId', {
|
||||
enumerable: true,
|
||||
@@ -167,5 +166,3 @@ DeviceInfo.DeviceVerification = {
|
||||
|
||||
const DeviceVerification = DeviceInfo.DeviceVerification;
|
||||
|
||||
/** */
|
||||
module.exports = DeviceInfo;
|
||||
|
||||
+573
-590
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { randomString } from '../randomstring';
|
||||
import {randomString} from '../randomstring';
|
||||
|
||||
const DEFAULT_ITERATIONS = 500000;
|
||||
|
||||
@@ -49,7 +49,7 @@ export async function keyFromPassphrase(password) {
|
||||
return { key, salt, iterations: DEFAULT_ITERATIONS };
|
||||
}
|
||||
|
||||
async function deriveKey(password, salt, iterations) {
|
||||
export async function deriveKey(password, salt, iterations) {
|
||||
const subtleCrypto = global.crypto.subtle;
|
||||
const TextEncoder = global.TextEncoder;
|
||||
if (!subtleCrypto || !TextEncoder) {
|
||||
|
||||
+37
-21
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
Copyright 2019 New Vector Ltd
|
||||
Copyright 2019 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.
|
||||
@@ -21,26 +22,24 @@ limitations under the License.
|
||||
* Utilities common to olm encryption algorithms
|
||||
*/
|
||||
|
||||
import Promise from 'bluebird';
|
||||
const anotherjson = require('another-json');
|
||||
|
||||
import logger from '../logger';
|
||||
const utils = require("../utils");
|
||||
import {logger} from '../logger';
|
||||
import * as utils from "../utils";
|
||||
import anotherjson from "another-json";
|
||||
|
||||
/**
|
||||
* matrix algorithm tag for olm
|
||||
*/
|
||||
module.exports.OLM_ALGORITHM = "m.olm.v1.curve25519-aes-sha2";
|
||||
export const OLM_ALGORITHM = "m.olm.v1.curve25519-aes-sha2";
|
||||
|
||||
/**
|
||||
* matrix algorithm tag for megolm
|
||||
*/
|
||||
module.exports.MEGOLM_ALGORITHM = "m.megolm.v1.aes-sha2";
|
||||
export const MEGOLM_ALGORITHM = "m.megolm.v1.aes-sha2";
|
||||
|
||||
/**
|
||||
* matrix algorithm tag for megolm backups
|
||||
*/
|
||||
module.exports.MEGOLM_BACKUP_ALGORITHM = "m.megolm_backup.v1.curve25519-aes-sha2";
|
||||
export const MEGOLM_BACKUP_ALGORITHM = "m.megolm_backup.v1.curve25519-aes-sha2";
|
||||
|
||||
|
||||
/**
|
||||
@@ -59,7 +58,7 @@ module.exports.MEGOLM_BACKUP_ALGORITHM = "m.megolm_backup.v1.curve25519-aes-sha2
|
||||
* Returns a promise which resolves (to undefined) when the payload
|
||||
* has been encrypted into `resultsObject`
|
||||
*/
|
||||
module.exports.encryptMessageForDevice = async function(
|
||||
export async function encryptMessageForDevice(
|
||||
resultsObject,
|
||||
ourUserId, ourDeviceId, olmDevice, recipientUserId, recipientDevice,
|
||||
payloadFields,
|
||||
@@ -112,7 +111,7 @@ module.exports.encryptMessageForDevice = async function(
|
||||
resultsObject[deviceKey] = await olmDevice.encryptMessage(
|
||||
deviceKey, sessionId, JSON.stringify(payload),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to make sure we have established olm sessions for the given devices.
|
||||
@@ -131,7 +130,7 @@ module.exports.encryptMessageForDevice = async function(
|
||||
* an Object mapping from userId to deviceId to
|
||||
* {@link module:crypto~OlmSessionResult}
|
||||
*/
|
||||
module.exports.ensureOlmSessionsForDevices = async function(
|
||||
export async function ensureOlmSessionsForDevices(
|
||||
olmDevice, baseApis, devicesByUser, force,
|
||||
) {
|
||||
const devicesWithoutSession = [
|
||||
@@ -263,12 +262,12 @@ module.exports.ensureOlmSessionsForDevices = async function(
|
||||
|
||||
await Promise.all(promises);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
async function _verifyKeyAndStartSession(olmDevice, oneTimeKey, userId, deviceInfo) {
|
||||
const deviceId = deviceInfo.deviceId;
|
||||
try {
|
||||
await _verifySignature(
|
||||
await verifySignature(
|
||||
olmDevice, oneTimeKey, userId, deviceId,
|
||||
deviceInfo.getFingerprint(),
|
||||
);
|
||||
@@ -303,8 +302,7 @@ async function _verifyKeyAndStartSession(olmDevice, oneTimeKey, userId, deviceIn
|
||||
*
|
||||
* @param {module:crypto/OlmDevice} olmDevice olm wrapper to use for verify op
|
||||
*
|
||||
* @param {Object} obj object to check signature on. Note that this will be
|
||||
* stripped of its 'signatures' and 'unsigned' properties.
|
||||
* @param {Object} obj object to check signature on.
|
||||
*
|
||||
* @param {string} signingUserId ID of the user whose signature should be checked
|
||||
*
|
||||
@@ -315,7 +313,7 @@ async function _verifyKeyAndStartSession(olmDevice, oneTimeKey, userId, deviceIn
|
||||
* Returns a promise which resolves (to undefined) if the the signature is good,
|
||||
* or rejects with an Error if it is bad.
|
||||
*/
|
||||
const _verifySignature = module.exports.verifySignature = async function(
|
||||
export async function verifySignature(
|
||||
olmDevice, obj, signingUserId, signingDeviceId, signingKey,
|
||||
) {
|
||||
const signKeyId = "ed25519:" + signingDeviceId;
|
||||
@@ -336,7 +334,7 @@ const _verifySignature = module.exports.verifySignature = async function(
|
||||
olmDevice.verifySignature(
|
||||
signingKey, json, signature,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a JSON object using public key cryptography
|
||||
@@ -348,7 +346,7 @@ const _verifySignature = module.exports.verifySignature = async function(
|
||||
* @param {string} pubkey The public key (ignored if key is a seed)
|
||||
* @returns {string} the signature for the object
|
||||
*/
|
||||
module.exports.pkSign = function(obj, key, userId, pubkey) {
|
||||
export function pkSign(obj, key, userId, pubkey) {
|
||||
let createdKey = false;
|
||||
if (key instanceof Uint8Array) {
|
||||
const keyObj = new global.Olm.PkSigning();
|
||||
@@ -372,7 +370,7 @@ module.exports.pkSign = function(obj, key, userId, pubkey) {
|
||||
key.free();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a signed JSON object
|
||||
@@ -380,7 +378,7 @@ module.exports.pkSign = function(obj, key, userId, pubkey) {
|
||||
* @param {string} pubkey The public key to use to verify
|
||||
* @param {string} userId The user ID who signed the object
|
||||
*/
|
||||
module.exports.pkVerify = function(obj, pubkey, userId) {
|
||||
export function pkVerify(obj, pubkey, userId) {
|
||||
const keyId = "ed25519:" + pubkey;
|
||||
if (!(obj.signatures && obj.signatures[userId] && obj.signatures[userId][keyId])) {
|
||||
throw new Error("No signature");
|
||||
@@ -398,4 +396,22 @@ module.exports.pkVerify = function(obj, pubkey, userId) {
|
||||
if (unsigned) obj.unsigned = unsigned;
|
||||
util.free();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a typed array of uint8 as base64.
|
||||
* @param {Uint8Array} uint8Array The data to encode.
|
||||
* @return {string} The base64.
|
||||
*/
|
||||
export function encodeBase64(uint8Array) {
|
||||
return Buffer.from(uint8Array).toString("base64");
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a base64 string to a typed array of uint8.
|
||||
* @param {string} base64 The base64 to decode.
|
||||
* @return {Uint8Array} The decoded data.
|
||||
*/
|
||||
export function decodeBase64(base64) {
|
||||
return Buffer.from(base64, "base64");
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2020 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.
|
||||
@@ -15,12 +16,10 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import Promise from 'bluebird';
|
||||
import {logger} from '../../logger';
|
||||
import * as utils from "../../utils";
|
||||
|
||||
import logger from '../../logger';
|
||||
import utils from '../../utils';
|
||||
|
||||
export const VERSION = 7;
|
||||
export const VERSION = 9;
|
||||
|
||||
/**
|
||||
* Implementation of a CryptoStore which is backed by an existing
|
||||
@@ -81,7 +80,7 @@ export class Backend {
|
||||
`enqueueing key request for ${requestBody.room_id} / ` +
|
||||
requestBody.session_id,
|
||||
);
|
||||
txn.oncomplete = () => { resolve(request); };
|
||||
txn.oncomplete = () => {resolve(request);};
|
||||
const store = txn.objectStore("outgoingRoomKeyRequests");
|
||||
store.add(request);
|
||||
});
|
||||
@@ -427,17 +426,107 @@ export class Backend {
|
||||
});
|
||||
}
|
||||
|
||||
async storeEndToEndSessionProblem(deviceKey, type, fixed) {
|
||||
const txn = this._db.transaction("session_problems", "readwrite");
|
||||
const objectStore = txn.objectStore("session_problems");
|
||||
objectStore.put({
|
||||
deviceKey,
|
||||
type,
|
||||
fixed,
|
||||
time: Date.now(),
|
||||
});
|
||||
return promiseifyTxn(txn);
|
||||
}
|
||||
|
||||
async getEndToEndSessionProblem(deviceKey, timestamp) {
|
||||
let result;
|
||||
const txn = this._db.transaction("session_problems", "readwrite");
|
||||
const objectStore = txn.objectStore("session_problems");
|
||||
const index = objectStore.index("deviceKey");
|
||||
const req = index.getAll(deviceKey);
|
||||
req.onsuccess = (event) => {
|
||||
const problems = req.result;
|
||||
if (!problems.length) {
|
||||
result = null;
|
||||
return;
|
||||
}
|
||||
problems.sort((a, b) => {
|
||||
return a.time - b.time;
|
||||
});
|
||||
const lastProblem = problems[problems.length - 1];
|
||||
for (const problem of problems) {
|
||||
if (problem.time > timestamp) {
|
||||
result = Object.assign({}, problem, {fixed: lastProblem.fixed});
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (lastProblem.fixed) {
|
||||
result = null;
|
||||
} else {
|
||||
result = lastProblem;
|
||||
}
|
||||
};
|
||||
await promiseifyTxn(txn);
|
||||
return result;
|
||||
}
|
||||
|
||||
// FIXME: we should probably prune this when devices get deleted
|
||||
async filterOutNotifiedErrorDevices(devices) {
|
||||
const txn = this._db.transaction("notified_error_devices", "readwrite");
|
||||
const objectStore = txn.objectStore("notified_error_devices");
|
||||
|
||||
const ret = [];
|
||||
|
||||
await Promise.all(devices.map((device) => {
|
||||
return new Promise((resolve) => {
|
||||
const {userId, deviceInfo} = device;
|
||||
const getReq = objectStore.get([userId, deviceInfo.deviceId]);
|
||||
getReq.onsuccess = function() {
|
||||
if (!getReq.result) {
|
||||
objectStore.put({userId, deviceId: deviceInfo.deviceId});
|
||||
ret.push(device);
|
||||
}
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
}));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Inbound group sessions
|
||||
|
||||
getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) {
|
||||
let session = false;
|
||||
let withheld = false;
|
||||
const objectStore = txn.objectStore("inbound_group_sessions");
|
||||
const getReq = objectStore.get([senderCurve25519Key, sessionId]);
|
||||
getReq.onsuccess = function() {
|
||||
try {
|
||||
if (getReq.result) {
|
||||
func(getReq.result.session);
|
||||
session = getReq.result.session;
|
||||
} else {
|
||||
func(null);
|
||||
session = null;
|
||||
}
|
||||
if (withheld !== false) {
|
||||
func(session, withheld);
|
||||
}
|
||||
} catch (e) {
|
||||
abortWithException(txn, e);
|
||||
}
|
||||
};
|
||||
|
||||
const withheldObjectStore = txn.objectStore("inbound_group_sessions_withheld");
|
||||
const withheldGetReq = withheldObjectStore.get([senderCurve25519Key, sessionId]);
|
||||
withheldGetReq.onsuccess = function() {
|
||||
try {
|
||||
if (withheldGetReq.result) {
|
||||
withheld = withheldGetReq.result.session;
|
||||
} else {
|
||||
withheld = null;
|
||||
}
|
||||
if (session !== false) {
|
||||
func(session, withheld);
|
||||
}
|
||||
} catch (e) {
|
||||
abortWithException(txn, e);
|
||||
@@ -501,6 +590,15 @@ export class Backend {
|
||||
});
|
||||
}
|
||||
|
||||
storeEndToEndInboundGroupSessionWithheld(
|
||||
senderCurve25519Key, sessionId, sessionData, txn,
|
||||
) {
|
||||
const objectStore = txn.objectStore("inbound_group_sessions_withheld");
|
||||
objectStore.put({
|
||||
senderCurve25519Key, sessionId, session: sessionData,
|
||||
});
|
||||
}
|
||||
|
||||
getEndToEndDeviceData(txn, func) {
|
||||
const objectStore = txn.objectStore("device_data");
|
||||
const getReq = objectStore.get("-");
|
||||
@@ -664,6 +762,21 @@ export function upgradeDatabase(db, oldVersion) {
|
||||
keyPath: ["senderCurve25519Key", "sessionId"],
|
||||
});
|
||||
}
|
||||
if (oldVersion < 8) {
|
||||
db.createObjectStore("inbound_group_sessions_withheld", {
|
||||
keyPath: ["senderCurve25519Key", "sessionId"],
|
||||
});
|
||||
}
|
||||
if (oldVersion < 9) {
|
||||
const problemsStore = db.createObjectStore("session_problems", {
|
||||
keyPath: ["deviceKey", "time"],
|
||||
});
|
||||
problemsStore.createIndex("deviceKey", "deviceKey");
|
||||
|
||||
db.createObjectStore("notified_error_devices", {
|
||||
keyPath: ["userId", "deviceId"],
|
||||
});
|
||||
}
|
||||
// Expand as needed.
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2020 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.
|
||||
@@ -15,11 +16,9 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import Promise from 'bluebird';
|
||||
|
||||
import logger from '../../logger';
|
||||
import LocalStorageCryptoStore from './localStorage-crypto-store';
|
||||
import MemoryCryptoStore from './memory-crypto-store';
|
||||
import {logger} from '../../logger';
|
||||
import {LocalStorageCryptoStore} from './localStorage-crypto-store';
|
||||
import {MemoryCryptoStore} from './memory-crypto-store';
|
||||
import * as IndexedDBCryptoStoreBackend from './indexeddb-crypto-store-backend';
|
||||
import {InvalidCryptoStoreError} from '../../errors';
|
||||
import * as IndexedDBHelpers from "../../indexeddb-helpers";
|
||||
@@ -36,7 +35,7 @@ import * as IndexedDBHelpers from "../../indexeddb-helpers";
|
||||
*
|
||||
* @implements {module:crypto/store/base~CryptoStore}
|
||||
*/
|
||||
export default class IndexedDBCryptoStore {
|
||||
export class IndexedDBCryptoStore {
|
||||
/**
|
||||
* Create a new IndexedDBCryptoStore
|
||||
*
|
||||
@@ -106,7 +105,10 @@ export default class IndexedDBCryptoStore {
|
||||
// we can fall back to a different backend.
|
||||
return backend.doTxn(
|
||||
'readonly',
|
||||
[IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS],
|
||||
[
|
||||
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS,
|
||||
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS_WITHHELD,
|
||||
],
|
||||
(txn) => {
|
||||
backend.getEndToEndInboundGroupSession('', '', txn, () => {});
|
||||
}).then(() => {
|
||||
@@ -407,6 +409,24 @@ export default class IndexedDBCryptoStore {
|
||||
});
|
||||
}
|
||||
|
||||
storeEndToEndSessionProblem(deviceKey, type, fixed) {
|
||||
return this._backendPromise.then(async (backend) => {
|
||||
await backend.storeEndToEndSessionProblem(deviceKey, type, fixed);
|
||||
});
|
||||
}
|
||||
|
||||
getEndToEndSessionProblem(deviceKey, timestamp) {
|
||||
return this._backendPromise.then(async (backend) => {
|
||||
return await backend.getEndToEndSessionProblem(deviceKey, timestamp);
|
||||
});
|
||||
}
|
||||
|
||||
filterOutNotifiedErrorDevices(devices) {
|
||||
return this._backendPromise.then(async (backend) => {
|
||||
return await backend.filterOutNotifiedErrorDevices(devices);
|
||||
});
|
||||
}
|
||||
|
||||
// Inbound group sessions
|
||||
|
||||
/**
|
||||
@@ -473,6 +493,16 @@ export default class IndexedDBCryptoStore {
|
||||
});
|
||||
}
|
||||
|
||||
storeEndToEndInboundGroupSessionWithheld(
|
||||
senderCurve25519Key, sessionId, sessionData, txn,
|
||||
) {
|
||||
this._backendPromise.then(backend => {
|
||||
backend.storeEndToEndInboundGroupSessionWithheld(
|
||||
senderCurve25519Key, sessionId, sessionData, txn,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// End-to-end device tracking
|
||||
|
||||
/**
|
||||
@@ -609,6 +639,8 @@ export default class IndexedDBCryptoStore {
|
||||
IndexedDBCryptoStore.STORE_ACCOUNT = 'account';
|
||||
IndexedDBCryptoStore.STORE_SESSIONS = 'sessions';
|
||||
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS = 'inbound_group_sessions';
|
||||
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS_WITHHELD
|
||||
= 'inbound_group_sessions_withheld';
|
||||
IndexedDBCryptoStore.STORE_DEVICE_DATA = 'device_data';
|
||||
IndexedDBCryptoStore.STORE_ROOMS = 'rooms';
|
||||
IndexedDBCryptoStore.STORE_BACKUP = 'sessions_needing_backup';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright 2017, 2018 New Vector Ltd
|
||||
Copyright 2020 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.
|
||||
@@ -14,10 +15,8 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import Promise from 'bluebird';
|
||||
|
||||
import logger from '../../logger';
|
||||
import MemoryCryptoStore from './memory-crypto-store';
|
||||
import {logger} from '../../logger';
|
||||
import {MemoryCryptoStore} from './memory-crypto-store';
|
||||
|
||||
/**
|
||||
* Internal module. Partial localStorage backed storage for e2e.
|
||||
@@ -32,8 +31,10 @@ import MemoryCryptoStore from './memory-crypto-store';
|
||||
const E2E_PREFIX = "crypto.";
|
||||
const KEY_END_TO_END_ACCOUNT = E2E_PREFIX + "account";
|
||||
const KEY_CROSS_SIGNING_KEYS = E2E_PREFIX + "cross_signing_keys";
|
||||
const KEY_NOTIFIED_ERROR_DEVICES = E2E_PREFIX + "notified_error_devices";
|
||||
const KEY_DEVICE_DATA = E2E_PREFIX + "device_data";
|
||||
const KEY_INBOUND_SESSION_PREFIX = E2E_PREFIX + "inboundgroupsessions/";
|
||||
const KEY_INBOUND_SESSION_WITHHELD_PREFIX = E2E_PREFIX + "inboundgroupsessions.withheld/";
|
||||
const KEY_ROOMS_PREFIX = E2E_PREFIX + "rooms/";
|
||||
const KEY_SESSIONS_NEEDING_BACKUP = E2E_PREFIX + "sessionsneedingbackup";
|
||||
|
||||
@@ -41,10 +42,18 @@ function keyEndToEndSessions(deviceKey) {
|
||||
return E2E_PREFIX + "sessions/" + deviceKey;
|
||||
}
|
||||
|
||||
function keyEndToEndSessionProblems(deviceKey) {
|
||||
return E2E_PREFIX + "session.problems/" + deviceKey;
|
||||
}
|
||||
|
||||
function keyEndToEndInboundGroupSession(senderKey, sessionId) {
|
||||
return KEY_INBOUND_SESSION_PREFIX + senderKey + "/" + sessionId;
|
||||
}
|
||||
|
||||
function keyEndToEndInboundGroupSessionWithheld(senderKey, sessionId) {
|
||||
return KEY_INBOUND_SESSION_WITHHELD_PREFIX + senderKey + "/" + sessionId;
|
||||
}
|
||||
|
||||
function keyEndToEndRoomsPrefix(roomId) {
|
||||
return KEY_ROOMS_PREFIX + roomId;
|
||||
}
|
||||
@@ -52,7 +61,7 @@ function keyEndToEndRoomsPrefix(roomId) {
|
||||
/**
|
||||
* @implements {module:crypto/store/base~CryptoStore}
|
||||
*/
|
||||
export default class LocalStorageCryptoStore extends MemoryCryptoStore {
|
||||
export class LocalStorageCryptoStore extends MemoryCryptoStore {
|
||||
constructor(webStore) {
|
||||
super();
|
||||
this.store = webStore;
|
||||
@@ -124,13 +133,71 @@ export default class LocalStorageCryptoStore extends MemoryCryptoStore {
|
||||
);
|
||||
}
|
||||
|
||||
async storeEndToEndSessionProblem(deviceKey, type, fixed) {
|
||||
const key = keyEndToEndSessionProblems(deviceKey);
|
||||
const problems = getJsonItem(this.store, key) || [];
|
||||
problems.push({type, fixed, time: Date.now()});
|
||||
problems.sort((a, b) => {
|
||||
return a.time - b.time;
|
||||
});
|
||||
setJsonItem(this.store, key, problems);
|
||||
}
|
||||
|
||||
async getEndToEndSessionProblem(deviceKey, timestamp) {
|
||||
const key = keyEndToEndSessionProblems(deviceKey);
|
||||
const problems = getJsonItem(this.store, key) || [];
|
||||
if (!problems.length) {
|
||||
return null;
|
||||
}
|
||||
const lastProblem = problems[problems.length - 1];
|
||||
for (const problem of problems) {
|
||||
if (problem.time > timestamp) {
|
||||
return Object.assign({}, problem, {fixed: lastProblem.fixed});
|
||||
}
|
||||
}
|
||||
if (lastProblem.fixed) {
|
||||
return null;
|
||||
} else {
|
||||
return lastProblem;
|
||||
}
|
||||
}
|
||||
|
||||
async filterOutNotifiedErrorDevices(devices) {
|
||||
const notifiedErrorDevices =
|
||||
getJsonItem(this.store, KEY_NOTIFIED_ERROR_DEVICES) || {};
|
||||
const ret = [];
|
||||
|
||||
for (const device of devices) {
|
||||
const {userId, deviceInfo} = device;
|
||||
if (userId in notifiedErrorDevices) {
|
||||
if (!(deviceInfo.deviceId in notifiedErrorDevices[userId])) {
|
||||
ret.push(device);
|
||||
notifiedErrorDevices[userId][deviceInfo.deviceId] = true;
|
||||
}
|
||||
} else {
|
||||
ret.push(device);
|
||||
notifiedErrorDevices[userId] = {[deviceInfo.deviceId]: true };
|
||||
}
|
||||
}
|
||||
|
||||
setJsonItem(this.store, KEY_NOTIFIED_ERROR_DEVICES, notifiedErrorDevices);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Inbound Group Sessions
|
||||
|
||||
getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) {
|
||||
func(getJsonItem(
|
||||
this.store,
|
||||
keyEndToEndInboundGroupSession(senderCurve25519Key, sessionId),
|
||||
));
|
||||
func(
|
||||
getJsonItem(
|
||||
this.store,
|
||||
keyEndToEndInboundGroupSession(senderCurve25519Key, sessionId),
|
||||
),
|
||||
getJsonItem(
|
||||
this.store,
|
||||
keyEndToEndInboundGroupSessionWithheld(senderCurve25519Key, sessionId),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
getAllEndToEndInboundGroupSessions(txn, func) {
|
||||
@@ -172,6 +239,16 @@ export default class LocalStorageCryptoStore extends MemoryCryptoStore {
|
||||
);
|
||||
}
|
||||
|
||||
storeEndToEndInboundGroupSessionWithheld(
|
||||
senderCurve25519Key, sessionId, sessionData, txn,
|
||||
) {
|
||||
setJsonItem(
|
||||
this.store,
|
||||
keyEndToEndInboundGroupSessionWithheld(senderCurve25519Key, sessionId),
|
||||
sessionData,
|
||||
);
|
||||
}
|
||||
|
||||
getEndToEndDeviceData(txn, func) {
|
||||
func(getJsonItem(
|
||||
this.store, KEY_DEVICE_DATA,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2020 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.
|
||||
@@ -15,10 +16,8 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import Promise from 'bluebird';
|
||||
|
||||
import logger from '../../logger';
|
||||
import utils from '../../utils';
|
||||
import {logger} from '../../logger';
|
||||
import * as utils from "../../utils";
|
||||
|
||||
/**
|
||||
* Internal module. in-memory storage for e2e.
|
||||
@@ -29,7 +28,7 @@ import utils from '../../utils';
|
||||
/**
|
||||
* @implements {module:crypto/store/base~CryptoStore}
|
||||
*/
|
||||
export default class MemoryCryptoStore {
|
||||
export class MemoryCryptoStore {
|
||||
constructor() {
|
||||
this._outgoingRoomKeyRequests = [];
|
||||
this._account = null;
|
||||
@@ -37,8 +36,13 @@ export default class MemoryCryptoStore {
|
||||
|
||||
// Map of {devicekey -> {sessionId -> session pickle}}
|
||||
this._sessions = {};
|
||||
// Map of {devicekey -> array of problems}
|
||||
this._sessionProblems = {};
|
||||
// Map of {userId -> deviceId -> true}
|
||||
this._notifiedErrorDevices = {};
|
||||
// Map of {senderCurve25519Key+'/'+sessionId -> session data object}
|
||||
this._inboundGroupSessions = {};
|
||||
this._inboundGroupSessionsWithheld = {};
|
||||
// Opaque device data object
|
||||
this._deviceData = null;
|
||||
// roomId -> Opaque roomInfo object
|
||||
@@ -69,7 +73,7 @@ export default class MemoryCryptoStore {
|
||||
getOrAddOutgoingRoomKeyRequest(request) {
|
||||
const requestBody = request.requestBody;
|
||||
|
||||
return Promise.try(() => {
|
||||
return utils.promiseTry(() => {
|
||||
// first see if we already have an entry for this request.
|
||||
const existing = this._getOutgoingRoomKeyRequest(requestBody);
|
||||
|
||||
@@ -275,10 +279,61 @@ export default class MemoryCryptoStore {
|
||||
deviceSessions[sessionId] = sessionInfo;
|
||||
}
|
||||
|
||||
async storeEndToEndSessionProblem(deviceKey, type, fixed) {
|
||||
const problems = this._sessionProblems[deviceKey]
|
||||
= this._sessionProblems[deviceKey] || [];
|
||||
problems.push({type, fixed, time: Date.now()});
|
||||
problems.sort((a, b) => {
|
||||
return a.time - b.time;
|
||||
});
|
||||
}
|
||||
|
||||
async getEndToEndSessionProblem(deviceKey, timestamp) {
|
||||
const problems = this._sessionProblems[deviceKey] || [];
|
||||
if (!problems.length) {
|
||||
return null;
|
||||
}
|
||||
const lastProblem = problems[problems.length - 1];
|
||||
for (const problem of problems) {
|
||||
if (problem.time > timestamp) {
|
||||
return Object.assign({}, problem, {fixed: lastProblem.fixed});
|
||||
}
|
||||
}
|
||||
if (lastProblem.fixed) {
|
||||
return null;
|
||||
} else {
|
||||
return lastProblem;
|
||||
}
|
||||
}
|
||||
|
||||
async filterOutNotifiedErrorDevices(devices) {
|
||||
const notifiedErrorDevices = this._notifiedErrorDevices;
|
||||
const ret = [];
|
||||
|
||||
for (const device of devices) {
|
||||
const {userId, deviceInfo} = device;
|
||||
if (userId in notifiedErrorDevices) {
|
||||
if (!(deviceInfo.deviceId in notifiedErrorDevices[userId])) {
|
||||
ret.push(device);
|
||||
notifiedErrorDevices[userId][deviceInfo.deviceId] = true;
|
||||
}
|
||||
} else {
|
||||
ret.push(device);
|
||||
notifiedErrorDevices[userId] = {[deviceInfo.deviceId]: true };
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Inbound Group Sessions
|
||||
|
||||
getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) {
|
||||
func(this._inboundGroupSessions[senderCurve25519Key+'/'+sessionId] || null);
|
||||
const k = senderCurve25519Key+'/'+sessionId;
|
||||
func(
|
||||
this._inboundGroupSessions[k] || null,
|
||||
this._inboundGroupSessionsWithheld[k] || null,
|
||||
);
|
||||
}
|
||||
|
||||
getAllEndToEndInboundGroupSessions(txn, func) {
|
||||
@@ -308,6 +363,13 @@ export default class MemoryCryptoStore {
|
||||
this._inboundGroupSessions[senderCurve25519Key+'/'+sessionId] = sessionData;
|
||||
}
|
||||
|
||||
storeEndToEndInboundGroupSessionWithheld(
|
||||
senderCurve25519Key, sessionId, sessionData, txn,
|
||||
) {
|
||||
const k = senderCurve25519Key+'/'+sessionId;
|
||||
this._inboundGroupSessionsWithheld[k] = sessionData;
|
||||
}
|
||||
|
||||
// Device Data
|
||||
|
||||
getEndToEndDeviceData(txn, func) {
|
||||
|
||||
+22
-103
@@ -21,13 +21,13 @@ limitations under the License.
|
||||
|
||||
import {MatrixEvent} from '../../models/event';
|
||||
import {EventEmitter} from 'events';
|
||||
import logger from '../../logger';
|
||||
import DeviceInfo from '../deviceinfo';
|
||||
import {logger} from '../../logger';
|
||||
import {DeviceInfo} from '../deviceinfo';
|
||||
import {newTimeoutError} from "./Error";
|
||||
|
||||
const timeoutException = new Error("Verification timed out");
|
||||
|
||||
export default class VerificationBase extends EventEmitter {
|
||||
export class VerificationBase extends EventEmitter {
|
||||
/**
|
||||
* Base class for verification methods.
|
||||
*
|
||||
@@ -40,51 +40,36 @@ export default class VerificationBase extends EventEmitter {
|
||||
*
|
||||
* @class
|
||||
*
|
||||
* @param {module:base-apis~Channel} channel the verification channel to send verification messages over.
|
||||
*
|
||||
* @param {module:base-apis~MatrixBaseApis} baseApis base matrix api interface
|
||||
*
|
||||
* @param {string} userId the user ID that is being verified
|
||||
*
|
||||
* @param {string} deviceId the device ID that is being verified
|
||||
*
|
||||
* @param {string} transactionId the transaction ID to be used when sending events
|
||||
*
|
||||
* @param {string} [roomId] the room to use for verification
|
||||
*
|
||||
* @param {object} [startEvent] the m.key.verification.start event that
|
||||
* initiated this verification, if any
|
||||
*
|
||||
* @param {object} [request] the key verification request object related to
|
||||
* this verification, if any
|
||||
*/
|
||||
constructor(baseApis, userId, deviceId, transactionId, roomId, startEvent, request) {
|
||||
constructor(channel, baseApis, userId, deviceId, startEvent, request) {
|
||||
super();
|
||||
this._channel = channel;
|
||||
this._baseApis = baseApis;
|
||||
this.userId = userId;
|
||||
this.deviceId = deviceId;
|
||||
this.transactionId = transactionId;
|
||||
if (typeof(roomId) === "string" || roomId instanceof String) {
|
||||
this.roomId = roomId;
|
||||
this.startEvent = startEvent;
|
||||
this.request = request;
|
||||
} else {
|
||||
// if room ID was omitted, but start event and request were not
|
||||
this.startEvent= roomId;
|
||||
this.request = startEvent;
|
||||
}
|
||||
this.startEvent = startEvent;
|
||||
this.request = request;
|
||||
|
||||
this.cancelled = false;
|
||||
this._done = false;
|
||||
this._promise = null;
|
||||
this._transactionTimeoutTimer = null;
|
||||
this._eventsSubscription = null;
|
||||
|
||||
// At this point, the verification request was received so start the timeout timer.
|
||||
this._resetTimer();
|
||||
|
||||
if (this.roomId) {
|
||||
this._sendWithTxnId = this._sendMessage;
|
||||
} else {
|
||||
this._sendWithTxnId = this._sendToDevice;
|
||||
}
|
||||
}
|
||||
|
||||
_resetTimer() {
|
||||
@@ -107,55 +92,8 @@ export default class VerificationBase extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
_contentFromEventWithTxnId(event) {
|
||||
if (this.roomId) { // verification as timeline event
|
||||
// ensure m.related_to is included in e2ee rooms
|
||||
// as the field is excluded from encryption
|
||||
const content = Object.assign({}, event.getContent());
|
||||
content["m.relates_to"] = event.getRelation();
|
||||
return content;
|
||||
} else { // verification as to_device event
|
||||
return event.getContent();
|
||||
}
|
||||
}
|
||||
|
||||
/* creates a content object with the transaction id added to it */
|
||||
_contentWithTxnId(content) {
|
||||
const copy = Object.assign({}, content);
|
||||
if (this.roomId) { // verification as timeline event
|
||||
copy["m.relates_to"] = {
|
||||
rel_type: "m.reference",
|
||||
event_id: this.transactionId,
|
||||
};
|
||||
} else { // verification as to_device event
|
||||
copy.transaction_id = this.transactionId;
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
_send(type, contentWithoutTxnId) {
|
||||
const content = this._contentWithTxnId(contentWithoutTxnId);
|
||||
return this._sendWithTxnId(type, content);
|
||||
}
|
||||
|
||||
/* send a message to the other participant, using to-device messages
|
||||
*/
|
||||
_sendToDevice(type, content) {
|
||||
if (this._done) {
|
||||
return Promise.reject(new Error("Verification is already done"));
|
||||
}
|
||||
return this._baseApis.sendToDevice(type, {
|
||||
[this.userId]: { [this.deviceId]: content },
|
||||
});
|
||||
}
|
||||
|
||||
/* send a message to the other participant, using in-roomm messages
|
||||
*/
|
||||
_sendMessage(type, content) {
|
||||
if (this._done) {
|
||||
return Promise.reject(new Error("Verification is already done"));
|
||||
}
|
||||
return this._baseApis.sendEvent(this.roomId, type, content);
|
||||
_send(type, uncompletedContent) {
|
||||
return this._channel.send(type, uncompletedContent);
|
||||
}
|
||||
|
||||
_waitForEvent(type) {
|
||||
@@ -173,10 +111,14 @@ export default class VerificationBase extends EventEmitter {
|
||||
if (this._done) {
|
||||
return;
|
||||
} else if (e.getType() === this._expectedEvent) {
|
||||
this._expectedEvent = undefined;
|
||||
this._rejectEvent = undefined;
|
||||
this._resetTimer();
|
||||
this._resolveEvent(e);
|
||||
// if we receive an expected m.key.verification.done, then just
|
||||
// ignore it, since we don't need to do anything about it
|
||||
if (this._expectedEvent !== "m.key.verification.done") {
|
||||
this._expectedEvent = undefined;
|
||||
this._rejectEvent = undefined;
|
||||
this._resetTimer();
|
||||
this._resolveEvent(e);
|
||||
}
|
||||
} else if (e.getType() === "m.key.verification.cancel") {
|
||||
const reject = this._reject;
|
||||
this._reject = undefined;
|
||||
@@ -199,7 +141,7 @@ export default class VerificationBase extends EventEmitter {
|
||||
done() {
|
||||
this._endTimer(); // always kill the activity timer
|
||||
if (!this._done) {
|
||||
if (this.roomId) {
|
||||
if (this._channel.needsDoneMessage) {
|
||||
// verification in DM requires a done message
|
||||
this._send("m.key.verification.done", {});
|
||||
}
|
||||
@@ -211,7 +153,7 @@ export default class VerificationBase extends EventEmitter {
|
||||
this._endTimer(); // always kill the activity timer
|
||||
if (!this._done) {
|
||||
this.cancelled = true;
|
||||
if (this.userId && this.deviceId && this.transactionId) {
|
||||
if (this.userId && this.deviceId) {
|
||||
// send a cancellation to the other user (if it wasn't
|
||||
// cancelled by the other user)
|
||||
if (e === timeoutException) {
|
||||
@@ -225,13 +167,11 @@ export default class VerificationBase extends EventEmitter {
|
||||
content.code = content.code || "m.unknown";
|
||||
content.reason = content.reason || content.body
|
||||
|| "Unknown reason";
|
||||
content.transaction_id = this.transactionId;
|
||||
this._send("m.key.verification.cancel", content);
|
||||
} else {
|
||||
this._send("m.key.verification.cancel", {
|
||||
code: "m.unknown",
|
||||
reason: content.body || "Unknown reason",
|
||||
transaction_id: this.transactionId,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -239,7 +179,6 @@ export default class VerificationBase extends EventEmitter {
|
||||
this._send("m.key.verification.cancel", {
|
||||
code: "m.unknown",
|
||||
reason: e.toString(),
|
||||
transaction_id: this.transactionId,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -248,10 +187,6 @@ export default class VerificationBase extends EventEmitter {
|
||||
// but no reject function. If cancel is called again, we'd error.
|
||||
if (this._reject) this._reject(e);
|
||||
} else {
|
||||
// unsubscribe from events, this happens in _reject usually but we don't have one here
|
||||
if (this._eventsSubscription) {
|
||||
this._eventsSubscription = this._eventsSubscription();
|
||||
}
|
||||
// FIXME: this causes an "Uncaught promise" console message
|
||||
// if nothing ends up chaining this promise.
|
||||
this._promise = Promise.reject(e);
|
||||
@@ -275,23 +210,11 @@ export default class VerificationBase extends EventEmitter {
|
||||
this._resolve = (...args) => {
|
||||
this._done = true;
|
||||
this._endTimer();
|
||||
if (this.handler) {
|
||||
// these listeners are attached in Crypto.acceptVerificationDM
|
||||
if (this._eventsSubscription) {
|
||||
this._eventsSubscription = this._eventsSubscription();
|
||||
}
|
||||
}
|
||||
resolve(...args);
|
||||
};
|
||||
this._reject = (...args) => {
|
||||
this._done = true;
|
||||
this._endTimer();
|
||||
if (this.handler) {
|
||||
// these listeners are attached in Crypto.acceptVerificationDM
|
||||
if (this._eventsSubscription) {
|
||||
this._eventsSubscription = this._eventsSubscription();
|
||||
}
|
||||
}
|
||||
reject(...args);
|
||||
};
|
||||
});
|
||||
@@ -344,8 +267,4 @@ export default class VerificationBase extends EventEmitter {
|
||||
await this._baseApis.setDeviceVerified(userId, deviceId);
|
||||
}
|
||||
}
|
||||
|
||||
setEventsSubscription(subscription) {
|
||||
this._eventsSubscription = subscription;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,3 +85,13 @@ export const newUserMismatchError = errorFactory("m.user_error", "User mismatch"
|
||||
export const newInvalidMessageError = errorFactory(
|
||||
"m.invalid_message", "Invalid message",
|
||||
);
|
||||
|
||||
export function errorFromEvent(event) {
|
||||
const content = event.getContent();
|
||||
if (content) {
|
||||
const {code, reason} = content;
|
||||
return {code, reason};
|
||||
} else {
|
||||
return {code: "Unknown error", reason: "m.unknown"};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,11 +19,11 @@ limitations under the License.
|
||||
* @module crypto/verification/QRCode
|
||||
*/
|
||||
|
||||
import Base from "./Base";
|
||||
import {VerificationBase as Base} from "./Base";
|
||||
import {
|
||||
errorFactory,
|
||||
newUserCancelledError,
|
||||
newKeyMismatchError,
|
||||
newUserCancelledError,
|
||||
newUserMismatchError,
|
||||
} from './Error';
|
||||
|
||||
|
||||
@@ -19,14 +19,14 @@ limitations under the License.
|
||||
* @module crypto/verification/SAS
|
||||
*/
|
||||
|
||||
import Base from "./Base";
|
||||
import {VerificationBase as Base} from "./Base";
|
||||
import anotherjson from 'another-json';
|
||||
import {
|
||||
errorFactory,
|
||||
newUserCancelledError,
|
||||
newUnknownMethodError,
|
||||
newKeyMismatchError,
|
||||
newInvalidMessageError,
|
||||
newKeyMismatchError,
|
||||
newUnknownMethodError,
|
||||
newUserCancelledError,
|
||||
} from './Error';
|
||||
|
||||
const EVENTS = [
|
||||
@@ -108,7 +108,7 @@ const emojiMapping = [
|
||||
["✏️", "pencil"], // 43
|
||||
["📎", "paperclip"], // 44
|
||||
["✂️", "scissors"], // 45
|
||||
["🔒", "padlock"], // 46
|
||||
["🔒", "lock"], // 46
|
||||
["🔑", "key"], // 47
|
||||
["🔨", "hammer"], // 48
|
||||
["☎️", "telephone"], // 49
|
||||
@@ -185,7 +185,11 @@ function intersection(anArray, aSet) {
|
||||
* @alias module:crypto/verification/SAS
|
||||
* @extends {module:crypto/verification/Base}
|
||||
*/
|
||||
export default class SAS extends Base {
|
||||
export class SAS extends Base {
|
||||
static get NAME() {
|
||||
return "m.sas.v1";
|
||||
}
|
||||
|
||||
get events() {
|
||||
return EVENTS;
|
||||
}
|
||||
@@ -205,7 +209,8 @@ export default class SAS extends Base {
|
||||
}
|
||||
|
||||
async _doSendVerification() {
|
||||
const initialMessage = this._contentWithTxnId({
|
||||
const type = "m.key.verification.start";
|
||||
const initialMessage = this._channel.completeContent(type, {
|
||||
method: SAS.NAME,
|
||||
from_device: this._baseApis.deviceId,
|
||||
key_agreement_protocols: KEY_AGREEMENT_LIST,
|
||||
@@ -216,8 +221,7 @@ export default class SAS extends Base {
|
||||
});
|
||||
// add the transaction id to the message beforehand because
|
||||
// it needs to be included in the commitment hash later on
|
||||
this._sendWithTxnId("m.key.verification.start", initialMessage);
|
||||
|
||||
this._channel.sendCompleted(type, initialMessage);
|
||||
|
||||
let e = await this._waitForEvent("m.key.verification.accept");
|
||||
let content = e.getContent();
|
||||
@@ -254,7 +258,7 @@ export default class SAS extends Base {
|
||||
const sasInfo = "MATRIX_KEY_VERIFICATION_SAS"
|
||||
+ this._baseApis.getUserId() + this._baseApis.deviceId
|
||||
+ this.userId + this.deviceId
|
||||
+ this.transactionId;
|
||||
+ this._channel.transactionId;
|
||||
const sasBytes = olmSAS.generate_bytes(sasInfo, 6);
|
||||
const verifySAS = new Promise((resolve, reject) => {
|
||||
this.emit("show_sas", {
|
||||
@@ -270,7 +274,14 @@ export default class SAS extends Base {
|
||||
|
||||
|
||||
[e] = await Promise.all([
|
||||
this._waitForEvent("m.key.verification.mac"),
|
||||
this._waitForEvent("m.key.verification.mac")
|
||||
.then((e) => {
|
||||
// we don't expect any more messages from the other
|
||||
// party, and they may send a m.key.verification.done
|
||||
// when they're done on their end
|
||||
this._expectedEvent = "m.key.verification.done";
|
||||
return e;
|
||||
}),
|
||||
verifySAS,
|
||||
]);
|
||||
content = e.getContent();
|
||||
@@ -283,7 +294,7 @@ export default class SAS extends Base {
|
||||
async _doRespondVerification() {
|
||||
// as m.related_to is not included in the encrypted content in e2e rooms,
|
||||
// we need to make sure it is added
|
||||
let content = this._contentFromEventWithTxnId(this.startEvent);
|
||||
let content = this._channel.completedContentFromEvent(this.startEvent);
|
||||
|
||||
// Note: we intersect using our pre-made lists, rather than the sets,
|
||||
// so that the result will be in our order of preference. Then
|
||||
@@ -331,7 +342,7 @@ export default class SAS extends Base {
|
||||
const sasInfo = "MATRIX_KEY_VERIFICATION_SAS"
|
||||
+ this.userId + this.deviceId
|
||||
+ this._baseApis.getUserId() + this._baseApis.deviceId
|
||||
+ this.transactionId;
|
||||
+ this._channel.transactionId;
|
||||
const sasBytes = olmSAS.generate_bytes(sasInfo, 6);
|
||||
const verifySAS = new Promise((resolve, reject) => {
|
||||
this.emit("show_sas", {
|
||||
@@ -347,7 +358,14 @@ export default class SAS extends Base {
|
||||
|
||||
|
||||
[e] = await Promise.all([
|
||||
this._waitForEvent("m.key.verification.mac"),
|
||||
this._waitForEvent("m.key.verification.mac")
|
||||
.then((e) => {
|
||||
// we don't expect any more messages from the other
|
||||
// party, and they may send a m.key.verification.done
|
||||
// when they're done on their end
|
||||
this._expectedEvent = "m.key.verification.done";
|
||||
return e;
|
||||
}),
|
||||
verifySAS,
|
||||
]);
|
||||
content = e.getContent();
|
||||
@@ -363,7 +381,7 @@ export default class SAS extends Base {
|
||||
const baseInfo = "MATRIX_KEY_VERIFICATION_MAC"
|
||||
+ this._baseApis.getUserId() + this._baseApis.deviceId
|
||||
+ this.userId + this.deviceId
|
||||
+ this.transactionId;
|
||||
+ this._channel.transactionId;
|
||||
|
||||
const deviceKeyId = `ed25519:${this._baseApis.deviceId}`;
|
||||
mac[deviceKeyId] = olmSAS[macMethods[method]](
|
||||
@@ -393,7 +411,7 @@ export default class SAS extends Base {
|
||||
const baseInfo = "MATRIX_KEY_VERIFICATION_MAC"
|
||||
+ this.userId + this.deviceId
|
||||
+ this._baseApis.getUserId() + this._baseApis.deviceId
|
||||
+ this.transactionId;
|
||||
+ this._channel.transactionId;
|
||||
|
||||
if (content.keys !== olmSAS[macMethods[method]](
|
||||
Object.keys(content.mac).sort().join(","),
|
||||
@@ -412,5 +430,3 @@ export default class SAS extends Base {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
SAS.NAME = "m.sas.v1";
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2019 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.
|
||||
*/
|
||||
|
||||
import {REQUEST_TYPE, START_TYPE, VerificationRequest} from "./VerificationRequest";
|
||||
|
||||
const MESSAGE_TYPE = "m.room.message";
|
||||
const M_REFERENCE = "m.reference";
|
||||
const M_RELATES_TO = "m.relates_to";
|
||||
|
||||
/**
|
||||
* A key verification channel that sends verification events in the timeline of a room.
|
||||
* Uses the event id of the initial m.key.verification.request event as a transaction id.
|
||||
*/
|
||||
export class InRoomChannel {
|
||||
/**
|
||||
* @param {MatrixClient} client the matrix client, to send messages with and get current user & device from.
|
||||
* @param {string} roomId id of the room where verification events should be posted in, should be a DM with the given user.
|
||||
* @param {string} userId id of user that the verification request is directed at, should be present in the room.
|
||||
*/
|
||||
constructor(client, roomId, userId) {
|
||||
this._client = client;
|
||||
this._roomId = roomId;
|
||||
this._userId = userId;
|
||||
this._requestEventId = null;
|
||||
}
|
||||
|
||||
/** Whether this channel needs m.key.verification.done messages to be sent after a successful verification */
|
||||
get needsDoneMessage() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** The transaction id generated/used by this verification channel */
|
||||
get transactionId() {
|
||||
return this._requestEventId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MatrixEvent} event the event to get the timestamp of
|
||||
* @return {number} the timestamp when the event was sent
|
||||
*/
|
||||
static getTimestamp(event) {
|
||||
return event.getTs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given event type should be allowed to initiate a new VerificationRequest over this channel
|
||||
* @param {string} type the event type to check
|
||||
* @returns {bool} boolean flag
|
||||
*/
|
||||
static canCreateRequest(type) {
|
||||
return type === REQUEST_TYPE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the transaction id used by a given key verification event, if any
|
||||
* @param {MatrixEvent} event the event
|
||||
* @returns {string} the transaction id
|
||||
*/
|
||||
static getTransactionId(event) {
|
||||
if (InRoomChannel.getEventType(event) === REQUEST_TYPE) {
|
||||
return event.getId();
|
||||
} else {
|
||||
const relation = event.getRelation();
|
||||
if (relation && relation.rel_type === M_REFERENCE) {
|
||||
return relation.event_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this event is a well-formed key verification event.
|
||||
* This only does checks that don't rely on the current state of a potentially already channel
|
||||
* so we can prevent channels being created by invalid events.
|
||||
* `handleEvent` can do more checks and choose to ignore invalid events.
|
||||
* @param {MatrixEvent} event the event to validate
|
||||
* @param {MatrixClient} client the client to get the current user and device id from
|
||||
* @returns {bool} whether the event is valid and should be passed to handleEvent
|
||||
*/
|
||||
static validateEvent(event, client) {
|
||||
const txnId = InRoomChannel.getTransactionId(event);
|
||||
if (typeof txnId !== "string" || txnId.length === 0) {
|
||||
return false;
|
||||
}
|
||||
const type = InRoomChannel.getEventType(event);
|
||||
const content = event.getContent();
|
||||
if (type === REQUEST_TYPE) {
|
||||
if (typeof content.to !== "string" || !content.to.length) {
|
||||
return false;
|
||||
}
|
||||
const ownUserId = client.getUserId();
|
||||
// ignore requests that are not direct to or sent by the syncing user
|
||||
if (event.getSender() !== ownUserId && content.to !== ownUserId) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return VerificationRequest.validateEvent(
|
||||
type, event, InRoomChannel.getTimestamp(event), client);
|
||||
}
|
||||
|
||||
/**
|
||||
* As m.key.verification.request events are as m.room.message events with the InRoomChannel
|
||||
* to have a fallback message in non-supporting clients, we map the real event type
|
||||
* to the symbolic one to keep things in unison with ToDeviceChannel
|
||||
* @param {MatrixEvent} event the event to get the type of
|
||||
* @returns {string} the "symbolic" event type
|
||||
*/
|
||||
static getEventType(event) {
|
||||
const type = event.getType();
|
||||
if (type === MESSAGE_TYPE) {
|
||||
const content = event.getContent();
|
||||
if (content) {
|
||||
const {msgtype} = content;
|
||||
if (msgtype === REQUEST_TYPE) {
|
||||
return REQUEST_TYPE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the state of the channel, request, and verifier in response to a key verification event.
|
||||
* @param {MatrixEvent} event to handle
|
||||
* @param {VerificationRequest} request the request to forward handling to
|
||||
* @returns {Promise} a promise that resolves when any requests as an anwser to the passed-in event are sent.
|
||||
*/
|
||||
async handleEvent(event, request) {
|
||||
const type = InRoomChannel.getEventType(event);
|
||||
// do validations that need state (roomId, userId),
|
||||
// ignore if invalid
|
||||
if (event.getRoomId() !== this._roomId || event.getSender() !== this._userId) {
|
||||
return;
|
||||
}
|
||||
// set transactionId when receiving a .request
|
||||
if (!this._requestEventId && type === REQUEST_TYPE) {
|
||||
this._requestEventId = event.getId();
|
||||
}
|
||||
|
||||
return await request.handleEvent(type, event, InRoomChannel.getTimestamp(event));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the transaction id (relation) back to a received event
|
||||
* so it has the same format as returned by `completeContent` before sending.
|
||||
* The relation can not appear on the event content because of encryption,
|
||||
* relations are excluded from encryption.
|
||||
* @param {MatrixEvent} event the received event
|
||||
* @returns {Object} the content object with the relation added again
|
||||
*/
|
||||
completedContentFromEvent(event) {
|
||||
// ensure m.related_to is included in e2ee rooms
|
||||
// as the field is excluded from encryption
|
||||
const content = Object.assign({}, event.getContent());
|
||||
content[M_RELATES_TO] = event.getRelation();
|
||||
return content;
|
||||
}
|
||||
/**
|
||||
* Add all the fields to content needed for sending it over this channel.
|
||||
* This is public so verification methods (SAS uses this) can get the exact
|
||||
* content that will be sent independent of the used channel,
|
||||
* as they need to calculate the hash of it.
|
||||
* @param {string} type the event type
|
||||
* @param {object} content the (incomplete) content
|
||||
* @returns {object} the complete content, as it will be sent.
|
||||
*/
|
||||
completeContent(type, content) {
|
||||
content = Object.assign({}, content);
|
||||
if (type === REQUEST_TYPE || type === START_TYPE) {
|
||||
content.from_device = this._client.getDeviceId();
|
||||
}
|
||||
if (type === REQUEST_TYPE) {
|
||||
// type is mapped to m.room.message in the send method
|
||||
content = {
|
||||
body: this._client.getUserId() + " is requesting to verify " +
|
||||
"your key, but your client does not support in-chat key " +
|
||||
"verification. You will need to use legacy key " +
|
||||
"verification to verify keys.",
|
||||
msgtype: REQUEST_TYPE,
|
||||
to: this._userId,
|
||||
from_device: content.from_device,
|
||||
methods: content.methods,
|
||||
};
|
||||
} else {
|
||||
content[M_RELATES_TO] = {
|
||||
rel_type: M_REFERENCE,
|
||||
event_id: this.transactionId,
|
||||
};
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an event over the channel with the content not having gone through `completeContent`.
|
||||
* @param {string} type the event type
|
||||
* @param {object} uncompletedContent the (incomplete) content
|
||||
* @returns {Promise} the promise of the request
|
||||
*/
|
||||
send(type, uncompletedContent) {
|
||||
const content = this.completeContent(type, uncompletedContent);
|
||||
return this.sendCompleted(type, content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an event over the channel with the content having gone through `completeContent` already.
|
||||
* @param {string} type the event type
|
||||
* @param {object} content
|
||||
* @returns {Promise} the promise of the request
|
||||
*/
|
||||
async sendCompleted(type, content) {
|
||||
let sendType = type;
|
||||
if (type === REQUEST_TYPE) {
|
||||
sendType = MESSAGE_TYPE;
|
||||
}
|
||||
const response = await this._client.sendEvent(this._roomId, sendType, content);
|
||||
if (type === REQUEST_TYPE) {
|
||||
this._requestEventId = response.event_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
Copyright 2019 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.
|
||||
*/
|
||||
|
||||
/** a key verification channel that wraps over an actual channel to pass it to a verifier,
|
||||
* to notify the VerificationRequest when the verifier tries to send anything over the channel.
|
||||
* This way, the VerificationRequest can update its state based on events sent by the verifier.
|
||||
* Anything that is not sending is just routing through to the wrapped channel.
|
||||
*/
|
||||
export class RequestCallbackChannel {
|
||||
constructor(request, channel) {
|
||||
this._request = request;
|
||||
this._channel = channel;
|
||||
}
|
||||
|
||||
get transactionId() {
|
||||
return this._channel.transactionId;
|
||||
}
|
||||
|
||||
get needsDoneMessage() {
|
||||
return this._channel.needsDoneMessage;
|
||||
}
|
||||
|
||||
handleEvent(event, request) {
|
||||
return this._channel.handleEvent(event, request);
|
||||
}
|
||||
|
||||
completedContentFromEvent(event) {
|
||||
return this._channel.completedContentFromEvent(event);
|
||||
}
|
||||
|
||||
completeContent(type, content) {
|
||||
return this._channel.completeContent(type, content);
|
||||
}
|
||||
|
||||
async send(type, uncompletedContent) {
|
||||
this._request.handleVerifierSend(type, uncompletedContent);
|
||||
const result = await this._channel.send(type, uncompletedContent);
|
||||
return result;
|
||||
}
|
||||
|
||||
async sendCompleted(type, content) {
|
||||
this._request.handleVerifierSend(type, content);
|
||||
const result = await this._channel.sendCompleted(type, content);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2019 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.
|
||||
*/
|
||||
|
||||
import {randomString} from '../../../randomstring';
|
||||
import {logger} from '../../../logger';
|
||||
import {
|
||||
CANCEL_TYPE,
|
||||
PHASE_STARTED,
|
||||
REQUEST_TYPE,
|
||||
START_TYPE,
|
||||
VerificationRequest,
|
||||
} from "./VerificationRequest";
|
||||
import {errorFromEvent, newUnexpectedMessageError} from "../Error";
|
||||
|
||||
/**
|
||||
* A key verification channel that sends verification events over to_device messages.
|
||||
* Generates its own transaction ids.
|
||||
*/
|
||||
export class ToDeviceChannel {
|
||||
// userId and devices of user we're about to verify
|
||||
constructor(client, userId, devices, transactionId = null, deviceId = null) {
|
||||
this._client = client;
|
||||
this._userId = userId;
|
||||
this._devices = devices;
|
||||
this.transactionId = transactionId;
|
||||
this._deviceId = deviceId;
|
||||
}
|
||||
|
||||
static getEventType(event) {
|
||||
return event.getType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the transaction id used by a given key verification event, if any
|
||||
* @param {MatrixEvent} event the event
|
||||
* @returns {string} the transaction id
|
||||
*/
|
||||
static getTransactionId(event) {
|
||||
const content = event.getContent();
|
||||
return content && content.transaction_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given event type should be allowed to initiate a new VerificationRequest over this channel
|
||||
* @param {string} type the event type to check
|
||||
* @returns {bool} boolean flag
|
||||
*/
|
||||
static canCreateRequest(type) {
|
||||
return type === REQUEST_TYPE || type === START_TYPE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this event is a well-formed key verification event.
|
||||
* This only does checks that don't rely on the current state of a potentially already channel
|
||||
* so we can prevent channels being created by invalid events.
|
||||
* `handleEvent` can do more checks and choose to ignore invalid events.
|
||||
* @param {MatrixEvent} event the event to validate
|
||||
* @param {MatrixClient} client the client to get the current user and device id from
|
||||
* @returns {bool} whether the event is valid and should be passed to handleEvent
|
||||
*/
|
||||
static validateEvent(event, client) {
|
||||
if (event.isCancelled()) {
|
||||
logger.warn("Ignoring flagged verification request from "
|
||||
+ event.getSender());
|
||||
return false;
|
||||
}
|
||||
const content = event.getContent();
|
||||
if (!content) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!content.transaction_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const type = event.getType();
|
||||
|
||||
if (type === REQUEST_TYPE) {
|
||||
if (!Number.isFinite(content.timestamp)) {
|
||||
return false;
|
||||
}
|
||||
if (event.getSender() === client.getUserId() &&
|
||||
content.from_device == client.getDeviceId()
|
||||
) {
|
||||
// ignore requests from ourselves, because it doesn't make sense for a
|
||||
// device to verify itself
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return VerificationRequest.validateEvent(
|
||||
type, event, ToDeviceChannel.getTimestamp(event), client);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MatrixEvent} event the event to get the timestamp of
|
||||
* @return {number} the timestamp when the event was sent
|
||||
*/
|
||||
static getTimestamp(event) {
|
||||
const content = event.getContent();
|
||||
return content && content.timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the state of the channel, request, and verifier in response to a key verification event.
|
||||
* @param {MatrixEvent} event to handle
|
||||
* @param {VerificationRequest} request the request to forward handling to
|
||||
* @returns {Promise} a promise that resolves when any requests as an anwser to the passed-in event are sent.
|
||||
*/
|
||||
async handleEvent(event, request) {
|
||||
const type = event.getType();
|
||||
const content = event.getContent();
|
||||
if (type === REQUEST_TYPE || type === START_TYPE) {
|
||||
if (!this.transactionId) {
|
||||
this.transactionId = content.transaction_id;
|
||||
}
|
||||
const deviceId = content.from_device;
|
||||
// adopt deviceId if not set before and valid
|
||||
if (!this._deviceId && this._devices.includes(deviceId)) {
|
||||
this._deviceId = deviceId;
|
||||
}
|
||||
// if no device id or different from addopted one, cancel with sender
|
||||
if (!this._deviceId || this._deviceId !== deviceId) {
|
||||
// also check that message came from the device we sent the request to earlier on
|
||||
// and do send a cancel message to that device
|
||||
// (but don't cancel the request for the device we should be talking to)
|
||||
const cancelContent =
|
||||
this.completeContent(errorFromEvent(newUnexpectedMessageError()));
|
||||
return this._sendToDevices(CANCEL_TYPE, cancelContent, [deviceId]);
|
||||
}
|
||||
}
|
||||
|
||||
const wasStarted = request.phase === PHASE_STARTED;
|
||||
await request.handleEvent(
|
||||
event.getType(), event, ToDeviceChannel.getTimestamp(event));
|
||||
const isStarted = request.phase === PHASE_STARTED;
|
||||
|
||||
// the request has picked a start event, tell the other devices about it
|
||||
if (type === START_TYPE && !wasStarted && isStarted && this._deviceId) {
|
||||
const nonChosenDevices = this._devices.filter(d => d !== this._deviceId);
|
||||
if (nonChosenDevices.length) {
|
||||
const message = this.completeContent({
|
||||
code: "m.accepted",
|
||||
reason: "Verification request accepted by another device",
|
||||
});
|
||||
await this._sendToDevices(CANCEL_TYPE, message, nonChosenDevices);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See {InRoomChannel.completedContentFromEvent} why this is needed.
|
||||
* @param {MatrixEvent} event the received event
|
||||
* @returns {Object} the content object
|
||||
*/
|
||||
completedContentFromEvent(event) {
|
||||
return event.getContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all the fields to content needed for sending it over this channel.
|
||||
* This is public so verification methods (SAS uses this) can get the exact
|
||||
* content that will be sent independent of the used channel,
|
||||
* as they need to calculate the hash of it.
|
||||
* @param {string} type the event type
|
||||
* @param {object} content the (incomplete) content
|
||||
* @returns {object} the complete content, as it will be sent.
|
||||
*/
|
||||
completeContent(type, content) {
|
||||
// make a copy
|
||||
content = Object.assign({}, content);
|
||||
if (this.transactionId) {
|
||||
content.transaction_id = this.transactionId;
|
||||
}
|
||||
if (type === REQUEST_TYPE || type === START_TYPE) {
|
||||
content.from_device = this._client.getDeviceId();
|
||||
}
|
||||
if (type === REQUEST_TYPE) {
|
||||
content.timestamp = Date.now();
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an event over the channel with the content not having gone through `completeContent`.
|
||||
* @param {string} type the event type
|
||||
* @param {object} uncompletedContent the (incomplete) content
|
||||
* @returns {Promise} the promise of the request
|
||||
*/
|
||||
send(type, uncompletedContent = {}) {
|
||||
// create transaction id when sending request
|
||||
if ((type === REQUEST_TYPE || type === START_TYPE) && !this.transactionId) {
|
||||
this.transactionId = ToDeviceChannel.makeTransactionId();
|
||||
}
|
||||
const content = this.completeContent(type, uncompletedContent);
|
||||
return this.sendCompleted(type, content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an event over the channel with the content having gone through `completeContent` already.
|
||||
* @param {string} type the event type
|
||||
* @param {object} content
|
||||
* @returns {Promise} the promise of the request
|
||||
*/
|
||||
sendCompleted(type, content) {
|
||||
if (type === REQUEST_TYPE) {
|
||||
return this._sendToDevices(type, content, this._devices);
|
||||
} else {
|
||||
return this._sendToDevices(type, content, [this._deviceId]);
|
||||
}
|
||||
}
|
||||
|
||||
_sendToDevices(type, content, devices) {
|
||||
if (devices.length) {
|
||||
const msgMap = {};
|
||||
for (const deviceId of devices) {
|
||||
msgMap[deviceId] = content;
|
||||
}
|
||||
|
||||
return this._client.sendToDevice(type, {[this._userId]: msgMap});
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow Crypto module to create and know the transaction id before the .start event gets sent.
|
||||
* @returns {string} the transaction id
|
||||
*/
|
||||
static makeTransactionId() {
|
||||
return randomString(32);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,414 @@
|
||||
/*
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2019 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.
|
||||
*/
|
||||
|
||||
import {logger} from '../../../logger';
|
||||
import {RequestCallbackChannel} from "./RequestCallbackChannel";
|
||||
import {EventEmitter} from 'events';
|
||||
import {
|
||||
errorFactory,
|
||||
errorFromEvent,
|
||||
newUnexpectedMessageError,
|
||||
newUnknownMethodError,
|
||||
} from "../Error";
|
||||
|
||||
// the recommended amount of time before a verification request
|
||||
// should be (automatically) cancelled without user interaction
|
||||
// and ignored.
|
||||
const VERIFICATION_REQUEST_TIMEOUT = 10 * 60 * 1000; //10m
|
||||
// to avoid almost expired verification notifications
|
||||
// from showing a notification and almost immediately
|
||||
// disappearing, also ignore verification requests that
|
||||
// are this amount of time away from expiring.
|
||||
const VERIFICATION_REQUEST_MARGIN = 3 * 1000; //3s
|
||||
|
||||
|
||||
export const EVENT_PREFIX = "m.key.verification.";
|
||||
export const REQUEST_TYPE = EVENT_PREFIX + "request";
|
||||
export const START_TYPE = EVENT_PREFIX + "start";
|
||||
export const CANCEL_TYPE = EVENT_PREFIX + "cancel";
|
||||
export const DONE_TYPE = EVENT_PREFIX + "done";
|
||||
// export const READY_TYPE = EVENT_PREFIX + "ready";
|
||||
|
||||
export const PHASE_UNSENT = 1;
|
||||
export const PHASE_REQUESTED = 2;
|
||||
// const PHASE_READY = 3;
|
||||
export const PHASE_STARTED = 4;
|
||||
export const PHASE_CANCELLED = 5;
|
||||
export const PHASE_DONE = 6;
|
||||
|
||||
|
||||
/**
|
||||
* State machine for verification requests.
|
||||
* Things that differ based on what channel is used to
|
||||
* send and receive verification events are put in `InRoomChannel` or `ToDeviceChannel`.
|
||||
* @event "change" whenever the state of the request object has changed.
|
||||
*/
|
||||
export class VerificationRequest extends EventEmitter {
|
||||
constructor(channel, verificationMethods, userId, client) {
|
||||
super();
|
||||
this.channel = channel;
|
||||
this._verificationMethods = verificationMethods;
|
||||
this._client = client;
|
||||
this._commonMethods = [];
|
||||
this._setPhase(PHASE_UNSENT, false);
|
||||
this._requestEvent = null;
|
||||
this._otherUserId = userId;
|
||||
this._initiatedByMe = null;
|
||||
this._startTimestamp = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stateless validation logic not specific to the channel.
|
||||
* Invoked by the same static method in either channel.
|
||||
* @param {string} type the "symbolic" event type, as returned by the `getEventType` function on the channel.
|
||||
* @param {MatrixEvent} event the event to validate. Don't call getType() on it but use the `type` parameter instead.
|
||||
* @param {number} timestamp the timestamp in milliseconds when this event was sent.
|
||||
* @param {MatrixClient} client the client to get the current user and device id from
|
||||
* @returns {bool} whether the event is valid and should be passed to handleEvent
|
||||
*/
|
||||
static validateEvent(type, event, timestamp, client) {
|
||||
const content = event.getContent();
|
||||
|
||||
if (!type.startsWith(EVENT_PREFIX)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type === REQUEST_TYPE) {
|
||||
if (!Array.isArray(content.methods)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (type === REQUEST_TYPE || type === START_TYPE) {
|
||||
if (typeof content.from_device !== "string" ||
|
||||
content.from_device.length === 0
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// a timestamp is not provided on all to_device events
|
||||
if (Number.isFinite(timestamp)) {
|
||||
const elapsed = Date.now() - timestamp;
|
||||
// ignore if event is too far in the past or too far in the future
|
||||
if (elapsed > (VERIFICATION_REQUEST_TIMEOUT - VERIFICATION_REQUEST_MARGIN) ||
|
||||
elapsed < -(VERIFICATION_REQUEST_TIMEOUT / 2)) {
|
||||
logger.log("received verification that is too old or from the future");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** once the phase is PHASE_STARTED, common methods supported by both sides */
|
||||
get methods() {
|
||||
return this._commonMethods;
|
||||
}
|
||||
|
||||
/** the timeout of the request, provided for compatibility with previous verification code */
|
||||
get timeout() {
|
||||
const elapsed = Date.now() - this._startTimestamp;
|
||||
return Math.max(0, VERIFICATION_REQUEST_TIMEOUT - elapsed);
|
||||
}
|
||||
|
||||
/** the m.key.verification.request event that started this request, provided for compatibility with previous verification code */
|
||||
get event() {
|
||||
return this._requestEvent;
|
||||
}
|
||||
|
||||
/** current phase of the request. Some properties might only be defined in a current phase. */
|
||||
get phase() {
|
||||
return this._phase;
|
||||
}
|
||||
|
||||
/** The verifier to do the actual verification, once the method has been established. Only defined when the `phase` is PHASE_STARTED. */
|
||||
get verifier() {
|
||||
return this._verifier;
|
||||
}
|
||||
|
||||
/** whether this request has sent it's initial event and needs more events to complete */
|
||||
get pending() {
|
||||
return this._phase !== PHASE_UNSENT
|
||||
&& this._phase !== PHASE_DONE
|
||||
&& this._phase !== PHASE_CANCELLED;
|
||||
}
|
||||
|
||||
/** Whether this request was initiated by the syncing user.
|
||||
* For InRoomChannel, this is who sent the .request event.
|
||||
* For ToDeviceChannel, this is who sent the .start event
|
||||
*/
|
||||
get initiatedByMe() {
|
||||
return this._initiatedByMe;
|
||||
}
|
||||
|
||||
/** the id of the user that initiated the request */
|
||||
get requestingUserId() {
|
||||
if (this.initiatedByMe) {
|
||||
return this._client.getUserId();
|
||||
} else {
|
||||
return this._otherUserId;
|
||||
}
|
||||
}
|
||||
|
||||
/** the id of the user that (will) receive(d) the request */
|
||||
get receivingUserId() {
|
||||
if (this.initiatedByMe) {
|
||||
return this._otherUserId;
|
||||
} else {
|
||||
return this._client.getUserId();
|
||||
}
|
||||
}
|
||||
|
||||
/* Start the key verification, creating a verifier and sending a .start event.
|
||||
* If no previous events have been sent, pass in `targetDevice` to set who to direct this request to.
|
||||
* @param {string} method the name of the verification method to use.
|
||||
* @param {string?} targetDevice.userId the id of the user to direct this request to
|
||||
* @param {string?} targetDevice.deviceId the id of the device to direct this request to
|
||||
* @returns {VerifierBase} the verifier of the given method
|
||||
*/
|
||||
beginKeyVerification(method, targetDevice = null) {
|
||||
// need to allow also when unsent in case of to_device
|
||||
if (!this._verifier) {
|
||||
if (this._hasValidPreStartPhase()) {
|
||||
// when called on a request that was initiated with .request event
|
||||
// check the method is supported by both sides
|
||||
if (this._commonMethods.length && !this._commonMethods.includes(method)) {
|
||||
throw newUnknownMethodError();
|
||||
}
|
||||
this._verifier = this._createVerifier(method, null, targetDevice);
|
||||
if (!this._verifier) {
|
||||
throw newUnknownMethodError();
|
||||
}
|
||||
}
|
||||
}
|
||||
return this._verifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* sends the initial .request event.
|
||||
* @returns {Promise} resolves when the event has been sent.
|
||||
*/
|
||||
async sendRequest() {
|
||||
if (this._phase === PHASE_UNSENT) {
|
||||
this._initiatedByMe = true;
|
||||
this._setPhase(PHASE_REQUESTED, false);
|
||||
const methods = [...this._verificationMethods.keys()];
|
||||
await this.channel.send(REQUEST_TYPE, {methods});
|
||||
this.emit("change");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the request, sending a cancellation to the other party
|
||||
* @param {string?} error.reason the error reason to send the cancellation with
|
||||
* @param {string?} error.code the error code to send the cancellation with
|
||||
* @returns {Promise} resolves when the event has been sent.
|
||||
*/
|
||||
async cancel({reason = "User declined", code = "m.user"} = {}) {
|
||||
if (this._phase !== PHASE_CANCELLED) {
|
||||
if (this._verifier) {
|
||||
return this._verifier.cancel(errorFactory(code, reason));
|
||||
} else {
|
||||
this._setPhase(PHASE_CANCELLED, false);
|
||||
await this.channel.send(CANCEL_TYPE, {code, reason});
|
||||
}
|
||||
this.emit("change");
|
||||
}
|
||||
}
|
||||
|
||||
/** @returns {Promise} with the verifier once it becomes available. Can be used after calling `sendRequest`. */
|
||||
waitForVerifier() {
|
||||
if (this.verifier) {
|
||||
return Promise.resolve(this.verifier);
|
||||
} else {
|
||||
return new Promise(resolve => {
|
||||
const checkVerifier = () => {
|
||||
if (this.verifier) {
|
||||
this.off("change", checkVerifier);
|
||||
resolve(this.verifier);
|
||||
}
|
||||
};
|
||||
this.on("change", checkVerifier);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_setPhase(phase, notify = true) {
|
||||
this._phase = phase;
|
||||
if (notify) {
|
||||
this.emit("change");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the state of the request and verifier in response to a key verification event.
|
||||
* @param {string} type the "symbolic" event type, as returned by the `getEventType` function on the channel.
|
||||
* @param {MatrixEvent} event the event to handle. Don't call getType() on it but use the `type` parameter instead.
|
||||
* @param {number} timestamp the timestamp in milliseconds when this event was sent.
|
||||
* @returns {Promise} a promise that resolves when any requests as an anwser to the passed-in event are sent.
|
||||
*/
|
||||
async handleEvent(type, event, timestamp) {
|
||||
const content = event.getContent();
|
||||
if (type === REQUEST_TYPE || type === START_TYPE) {
|
||||
if (this._startTimestamp === null) {
|
||||
this._startTimestamp = timestamp;
|
||||
}
|
||||
}
|
||||
if (type === REQUEST_TYPE) {
|
||||
await this._handleRequest(content, event);
|
||||
} else if (type === START_TYPE) {
|
||||
await this._handleStart(content, event);
|
||||
}
|
||||
|
||||
if (this._verifier) {
|
||||
if (type === CANCEL_TYPE || (this._verifier.events
|
||||
&& this._verifier.events.includes(type))) {
|
||||
this._verifier.handleEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
if (type === CANCEL_TYPE) {
|
||||
this._handleCancel();
|
||||
} else if (type === DONE_TYPE) {
|
||||
this._handleDone();
|
||||
}
|
||||
}
|
||||
|
||||
async _handleRequest(content, event) {
|
||||
if (this._phase === PHASE_UNSENT) {
|
||||
const otherMethods = content.methods;
|
||||
this._commonMethods = otherMethods.
|
||||
filter(m => this._verificationMethods.has(m));
|
||||
this._requestEvent = event;
|
||||
this._initiatedByMe = this._wasSentByMe(event);
|
||||
this._setPhase(PHASE_REQUESTED);
|
||||
} else if (this._phase !== PHASE_REQUESTED) {
|
||||
logger.warn("Ignoring flagged verification request from " +
|
||||
event.getSender());
|
||||
await this.cancel(errorFromEvent(newUnexpectedMessageError()));
|
||||
}
|
||||
}
|
||||
|
||||
_hasValidPreStartPhase() {
|
||||
return this._phase === PHASE_REQUESTED ||
|
||||
(
|
||||
this.channel.constructor.canCreateRequest(START_TYPE) &&
|
||||
this._phase === PHASE_UNSENT
|
||||
);
|
||||
}
|
||||
|
||||
async _handleStart(content, event) {
|
||||
if (this._hasValidPreStartPhase()) {
|
||||
const {method} = content;
|
||||
if (!this._verificationMethods.has(method)) {
|
||||
await this.cancel(errorFromEvent(newUnknownMethodError()));
|
||||
} else {
|
||||
// if not in requested phase
|
||||
if (this.phase === PHASE_UNSENT) {
|
||||
this._initiatedByMe = this._wasSentByMe(event);
|
||||
}
|
||||
this._verifier = this._createVerifier(method, event);
|
||||
this._setPhase(PHASE_STARTED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by RequestCallbackChannel when the verifier sends an event
|
||||
* @param {string} type the "symbolic" event type
|
||||
* @param {object} content the completed or uncompleted content for the event to be sent
|
||||
*/
|
||||
handleVerifierSend(type, content) {
|
||||
if (type === CANCEL_TYPE) {
|
||||
this._handleCancel();
|
||||
} else if (type === START_TYPE) {
|
||||
if (this._phase === PHASE_UNSENT || this._phase === PHASE_REQUESTED) {
|
||||
// if unsent, we're sending a (first) .start event and hence requesting the verification.
|
||||
// in any other situation, the request was initiated by the other party.
|
||||
this._initiatedByMe = this.phase === PHASE_UNSENT;
|
||||
this._setPhase(PHASE_STARTED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_handleCancel() {
|
||||
if (this._phase !== PHASE_CANCELLED) {
|
||||
this._setPhase(PHASE_CANCELLED);
|
||||
}
|
||||
}
|
||||
|
||||
_handleDone() {
|
||||
if (this._phase === PHASE_STARTED) {
|
||||
this._setPhase(PHASE_DONE);
|
||||
}
|
||||
}
|
||||
|
||||
_createVerifier(method, startEvent = null, targetDevice = null) {
|
||||
const startSentByMe = startEvent && this._wasSentByMe(startEvent);
|
||||
const {userId, deviceId} = this._getVerifierTarget(startEvent, targetDevice);
|
||||
|
||||
const VerifierCtor = this._verificationMethods.get(method);
|
||||
if (!VerifierCtor) {
|
||||
console.warn("could not find verifier constructor for method", method);
|
||||
return;
|
||||
}
|
||||
// invokes handleVerifierSend when verifier sends something
|
||||
const callbackMedium = new RequestCallbackChannel(this, this.channel);
|
||||
return new VerifierCtor(
|
||||
callbackMedium,
|
||||
this._client,
|
||||
userId,
|
||||
deviceId,
|
||||
startSentByMe ? null : startEvent,
|
||||
);
|
||||
}
|
||||
|
||||
_getVerifierTarget(startEvent, targetDevice) {
|
||||
// targetDevice should be set when creating a verifier for to_device before the .start event has been sent,
|
||||
// so the userId and deviceId are provided
|
||||
if (targetDevice) {
|
||||
return targetDevice;
|
||||
} else {
|
||||
let targetEvent;
|
||||
if (startEvent && !this._wasSentByMe(startEvent)) {
|
||||
targetEvent = startEvent;
|
||||
} else if (this._requestEvent && !this._wasSentByMe(this._requestEvent)) {
|
||||
targetEvent = this._requestEvent;
|
||||
} else {
|
||||
throw new Error(
|
||||
"can't determine who the verifier should be targeted at. " +
|
||||
"No .request or .start event and no targetDevice");
|
||||
}
|
||||
const userId = targetEvent.getSender();
|
||||
const content = targetEvent.getContent();
|
||||
const deviceId = content && content.from_device;
|
||||
return {userId, deviceId};
|
||||
}
|
||||
}
|
||||
|
||||
// only for .request and .start
|
||||
_wasSentByMe(event) {
|
||||
if (event.getSender() !== this._client.getUserId()) {
|
||||
return false;
|
||||
}
|
||||
const content = event.getContent();
|
||||
if (!content || content.from_device !== this._client.getDeviceId()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
Copyright 2019 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.
|
||||
@@ -13,7 +14,7 @@ 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.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* @module filter-component
|
||||
*/
|
||||
@@ -45,7 +46,7 @@ function _matches_wildcard(actual_value, filter_value) {
|
||||
* @constructor
|
||||
* @param {Object} filter_json the definition of this filter JSON, e.g. { 'contains_url': true }
|
||||
*/
|
||||
function FilterComponent(filter_json) {
|
||||
export function FilterComponent(filter_json) {
|
||||
this.filter_json = filter_json;
|
||||
|
||||
this.types = filter_json.types || null;
|
||||
@@ -141,6 +142,3 @@ FilterComponent.prototype.filter = function(events) {
|
||||
FilterComponent.prototype.limit = function() {
|
||||
return this.filter_json.limit !== undefined ? this.filter_json.limit : 10;
|
||||
};
|
||||
|
||||
/** The FilterComponent class */
|
||||
module.exports = FilterComponent;
|
||||
|
||||
+4
-6
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2019 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.
|
||||
@@ -13,12 +14,12 @@ 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.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* @module filter
|
||||
*/
|
||||
|
||||
const FilterComponent = require("./filter-component");
|
||||
import {FilterComponent} from "./filter-component";
|
||||
|
||||
/**
|
||||
* @param {Object} obj
|
||||
@@ -45,7 +46,7 @@ function setProp(obj, keyNesting, val) {
|
||||
* @prop {string} userId The user ID of the filter
|
||||
* @prop {?string} filterId The filter ID
|
||||
*/
|
||||
function Filter(userId, filterId) {
|
||||
export function Filter(userId, filterId) {
|
||||
this.userId = userId;
|
||||
this.filterId = filterId;
|
||||
this.definition = {};
|
||||
@@ -198,6 +199,3 @@ Filter.fromJson = function(userId, filterId, jsonObj) {
|
||||
filter.setDefinition(jsonObj);
|
||||
return filter;
|
||||
};
|
||||
|
||||
/** The Filter class */
|
||||
module.exports = Filter;
|
||||
|
||||
+22
-24
@@ -14,21 +14,20 @@ 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.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This is an internal module. See {@link MatrixHttpApi} for the public class.
|
||||
* @module http-api
|
||||
*/
|
||||
import Promise from 'bluebird';
|
||||
const parseContentType = require('content-type').parse;
|
||||
|
||||
const utils = require("./utils");
|
||||
import logger from './logger';
|
||||
import {parse as parseContentType} from "content-type";
|
||||
import * as utils from "./utils";
|
||||
import {logger} from './logger';
|
||||
|
||||
// we use our own implementation of setTimeout, so that if we get suspended in
|
||||
// the middle of a /sync, we cancel the sync as soon as we awake, rather than
|
||||
// waiting for the delay to elapse.
|
||||
const callbacks = require("./realtime-callbacks");
|
||||
import * as callbacks from "./realtime-callbacks";
|
||||
|
||||
/*
|
||||
TODO:
|
||||
@@ -39,27 +38,27 @@ TODO:
|
||||
/**
|
||||
* A constant representing the URI path for release 0 of the Client-Server HTTP API.
|
||||
*/
|
||||
module.exports.PREFIX_R0 = "/_matrix/client/r0";
|
||||
export const PREFIX_R0 = "/_matrix/client/r0";
|
||||
|
||||
/**
|
||||
* A constant representing the URI path for as-yet unspecified Client-Server HTTP APIs.
|
||||
*/
|
||||
module.exports.PREFIX_UNSTABLE = "/_matrix/client/unstable";
|
||||
export const PREFIX_UNSTABLE = "/_matrix/client/unstable";
|
||||
|
||||
/**
|
||||
* URI path for v1 of the the identity API
|
||||
*/
|
||||
module.exports.PREFIX_IDENTITY_V1 = "/_matrix/identity/api/v1";
|
||||
export const PREFIX_IDENTITY_V1 = "/_matrix/identity/api/v1";
|
||||
|
||||
/**
|
||||
* URI path for the v2 identity API
|
||||
*/
|
||||
module.exports.PREFIX_IDENTITY_V2 = "/_matrix/identity/v2";
|
||||
export const PREFIX_IDENTITY_V2 = "/_matrix/identity/v2";
|
||||
|
||||
/**
|
||||
* URI path for the media repo API
|
||||
*/
|
||||
module.exports.PREFIX_MEDIA_R0 = "/_matrix/media/r0";
|
||||
export const PREFIX_MEDIA_R0 = "/_matrix/media/r0";
|
||||
|
||||
/**
|
||||
* Construct a MatrixHttpApi.
|
||||
@@ -86,16 +85,16 @@ module.exports.PREFIX_MEDIA_R0 = "/_matrix/media/r0";
|
||||
* @param {boolean} [opts.useAuthorizationHeader = false] Set to true to use
|
||||
* Authorization header instead of query param to send the access token to the server.
|
||||
*/
|
||||
module.exports.MatrixHttpApi = function MatrixHttpApi(event_emitter, opts) {
|
||||
export function MatrixHttpApi(event_emitter, opts) {
|
||||
utils.checkObjectHasKeys(opts, ["baseUrl", "request", "prefix"]);
|
||||
opts.onlyData = opts.onlyData || false;
|
||||
this.event_emitter = event_emitter;
|
||||
this.opts = opts;
|
||||
this.useAuthorizationHeader = Boolean(opts.useAuthorizationHeader);
|
||||
this.uploads = [];
|
||||
};
|
||||
}
|
||||
|
||||
module.exports.MatrixHttpApi.prototype = {
|
||||
MatrixHttpApi.prototype = {
|
||||
/**
|
||||
* Sets the baase URL for the identity server
|
||||
* @param {string} url The new base url
|
||||
@@ -256,7 +255,7 @@ module.exports.MatrixHttpApi.prototype = {
|
||||
}
|
||||
|
||||
if (global.XMLHttpRequest) {
|
||||
const defer = Promise.defer();
|
||||
const defer = utils.defer();
|
||||
const xhr = new global.XMLHttpRequest();
|
||||
upload.xhr = xhr;
|
||||
const cb = requestCallback(defer, opts.callback, this.opts.onlyData);
|
||||
@@ -418,7 +417,7 @@ module.exports.MatrixHttpApi.prototype = {
|
||||
opts.headers['Authorization'] = `Bearer ${accessToken}`;
|
||||
}
|
||||
|
||||
const defer = Promise.defer();
|
||||
const defer = utils.defer();
|
||||
this.opts.request(
|
||||
opts,
|
||||
requestCallback(defer, callback, this.opts.onlyData),
|
||||
@@ -682,7 +681,7 @@ module.exports.MatrixHttpApi.prototype = {
|
||||
}
|
||||
}
|
||||
|
||||
const defer = Promise.defer();
|
||||
const defer = utils.defer();
|
||||
|
||||
let timeoutId;
|
||||
let timedOut = false;
|
||||
@@ -699,7 +698,7 @@ module.exports.MatrixHttpApi.prototype = {
|
||||
if (req && req.abort) {
|
||||
req.abort();
|
||||
}
|
||||
defer.reject(new module.exports.MatrixError({
|
||||
defer.reject(new MatrixError({
|
||||
error: "Locally timed out waiting for a response",
|
||||
errcode: "ORG.MATRIX.JSSDK_TIMEOUT",
|
||||
timeout: localTimeoutMs,
|
||||
@@ -837,7 +836,7 @@ function parseErrorResponse(response, body) {
|
||||
if (contentType) {
|
||||
if (contentType.type === 'application/json') {
|
||||
const jsonBody = typeof(body) === 'object' ? body : JSON.parse(body);
|
||||
err = new module.exports.MatrixError(jsonBody);
|
||||
err = new MatrixError(jsonBody);
|
||||
} else if (contentType.type === 'text/plain') {
|
||||
err = new Error(`Server returned ${httpStatus} error: ${body}`);
|
||||
}
|
||||
@@ -892,13 +891,12 @@ function getResponseContentType(response) {
|
||||
* @prop {Object} data The raw Matrix error JSON used to construct this object.
|
||||
* @prop {integer} httpStatus The numeric HTTP status code given
|
||||
*/
|
||||
module.exports.MatrixError = function MatrixError(errorJson) {
|
||||
export function MatrixError(errorJson) {
|
||||
errorJson = errorJson || {};
|
||||
this.errcode = errorJson.errcode;
|
||||
this.name = errorJson.errcode || "Unknown error code";
|
||||
this.message = errorJson.error || "Unknown message";
|
||||
this.data = errorJson;
|
||||
};
|
||||
module.exports.MatrixError.prototype = Object.create(Error.prototype);
|
||||
/** */
|
||||
module.exports.MatrixError.prototype.constructor = module.exports.MatrixError;
|
||||
}
|
||||
MatrixError.prototype = Object.create(Error.prototype);
|
||||
MatrixError.prototype.constructor = MatrixError;
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
Copyright 2019 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.
|
||||
*/
|
||||
|
||||
import * as matrixcs from "./matrix";
|
||||
import * as utils from "./utils";
|
||||
|
||||
matrixcs.request(import("request"));
|
||||
utils.runPolyfills();
|
||||
|
||||
export * from "./matrix";
|
||||
export default matrixcs;
|
||||
@@ -14,8 +14,6 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import Promise from 'bluebird';
|
||||
|
||||
/**
|
||||
* Check if an IndexedDB database exists. The only way to do so is to try opening it, so
|
||||
* we do that and then delete it did not exist before.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2019 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.
|
||||
@@ -20,5 +21,5 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
/** The {@link module:indexeddb-store-worker~IndexedDBStoreWorker} class. */
|
||||
module.exports.IndexedDBStoreWorker = require("./store/indexeddb-store-worker.js");
|
||||
export {IndexedDBStoreWorker} from "./store/indexeddb-store-worker";
|
||||
|
||||
|
||||
@@ -15,14 +15,12 @@ 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.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
/** @module interactive-auth */
|
||||
import Promise from 'bluebird';
|
||||
const url = require("url");
|
||||
|
||||
const utils = require("./utils");
|
||||
import logger from './logger';
|
||||
import url from "url";
|
||||
import * as utils from "./utils";
|
||||
import {logger} from './logger';
|
||||
|
||||
const EMAIL_STAGE_TYPE = "m.login.email.identity";
|
||||
const MSISDN_STAGE_TYPE = "m.login.msisdn";
|
||||
@@ -104,7 +102,7 @@ const MSISDN_STAGE_TYPE = "m.login.msisdn";
|
||||
* attemptAuth promise.
|
||||
*
|
||||
*/
|
||||
function InteractiveAuth(opts) {
|
||||
export function InteractiveAuth(opts) {
|
||||
this._matrixClient = opts.matrixClient;
|
||||
this._data = opts.authData || {};
|
||||
this._requestCallback = opts.doRequest;
|
||||
@@ -511,6 +509,3 @@ InteractiveAuth.prototype = {
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
/** */
|
||||
module.exports = InteractiveAuth;
|
||||
|
||||
+8
-6
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright 2018 André Jaenisch
|
||||
Copyright 2019 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.
|
||||
@@ -17,7 +18,8 @@ limitations under the License.
|
||||
/**
|
||||
* @module logger
|
||||
*/
|
||||
const log = require("loglevel");
|
||||
|
||||
import log from "loglevel";
|
||||
|
||||
// This is to demonstrate, that you can use any namespace you want.
|
||||
// Namespaces allow you to turn on/off the logging for specific parts of the
|
||||
@@ -25,12 +27,12 @@ const log = require("loglevel");
|
||||
// An idea would be to control this via an environment variable (on Node.js).
|
||||
// See https://www.npmjs.com/package/debug to see how this could be implemented
|
||||
// Part of #332 is introducing a logging library in the first place.
|
||||
const DEFAULT_NAME_SPACE = "matrix";
|
||||
const logger = log.getLogger(DEFAULT_NAME_SPACE);
|
||||
logger.setLevel(log.levels.DEBUG);
|
||||
const DEFAULT_NAMESPACE = "matrix";
|
||||
|
||||
/**
|
||||
* Drop-in replacement for <code>console</code> using {@link https://www.npmjs.com/package/loglevel|loglevel}.
|
||||
* Can be tailored down to specific use cases if needed.
|
||||
*/
|
||||
module.exports = logger;
|
||||
*/
|
||||
export const logger = log.getLogger(DEFAULT_NAMESPACE);
|
||||
logger.setLevel(log.levels.DEBUG);
|
||||
|
||||
|
||||
+56
-119
@@ -15,129 +15,66 @@ 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.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
/** The {@link module:ContentHelpers} object */
|
||||
module.exports.ContentHelpers = require("./content-helpers");
|
||||
/** The {@link module:models/event.MatrixEvent|MatrixEvent} class. */
|
||||
module.exports.MatrixEvent = require("./models/event").MatrixEvent;
|
||||
/** The {@link module:models/event.EventStatus|EventStatus} enum. */
|
||||
module.exports.EventStatus = require("./models/event").EventStatus;
|
||||
/** The {@link module:store/memory.MemoryStore|MemoryStore} class. */
|
||||
module.exports.MemoryStore = require("./store/memory").MemoryStore;
|
||||
/**
|
||||
* The {@link module:store/memory.MemoryStore|MemoryStore} class was previously
|
||||
* exported as `MatrixInMemoryStore`, so this is preserved for SDK consumers.
|
||||
* @deprecated Prefer `MemoryStore` going forward.
|
||||
*/
|
||||
module.exports.MatrixInMemoryStore = module.exports.MemoryStore;
|
||||
/** The {@link module:store/indexeddb.IndexedDBStore|IndexedDBStore} class. */
|
||||
module.exports.IndexedDBStore = require("./store/indexeddb").IndexedDBStore;
|
||||
/** The {@link module:store/indexeddb.IndexedDBStoreBackend|IndexedDBStoreBackend} class. */
|
||||
module.exports.IndexedDBStoreBackend = require("./store/indexeddb").IndexedDBStoreBackend;
|
||||
/** The {@link module:sync-accumulator.SyncAccumulator|SyncAccumulator} class. */
|
||||
module.exports.SyncAccumulator = require("./sync-accumulator");
|
||||
/** The {@link module:http-api.MatrixHttpApi|MatrixHttpApi} class. */
|
||||
module.exports.MatrixHttpApi = require("./http-api").MatrixHttpApi;
|
||||
/** The {@link module:http-api.MatrixError|MatrixError} class. */
|
||||
module.exports.MatrixError = require("./http-api").MatrixError;
|
||||
/** The {@link module:errors.InvalidStoreError|InvalidStoreError} class. */
|
||||
module.exports.InvalidStoreError = require("./errors").InvalidStoreError;
|
||||
/** The {@link module:client.MatrixClient|MatrixClient} class. */
|
||||
module.exports.MatrixClient = require("./client").MatrixClient;
|
||||
/** The {@link module:models/room|Room} class. */
|
||||
module.exports.Room = require("./models/room");
|
||||
/** The {@link module:models/group|Group} class. */
|
||||
module.exports.Group = require("./models/group");
|
||||
/** The {@link module:models/event-timeline~EventTimeline} class. */
|
||||
module.exports.EventTimeline = require("./models/event-timeline");
|
||||
/** The {@link module:models/event-timeline-set~EventTimelineSet} class. */
|
||||
module.exports.EventTimelineSet = require("./models/event-timeline-set");
|
||||
/** The {@link module:models/room-member|RoomMember} class. */
|
||||
module.exports.RoomMember = require("./models/room-member");
|
||||
/** The {@link module:models/room-state~RoomState|RoomState} class. */
|
||||
module.exports.RoomState = require("./models/room-state");
|
||||
/** The {@link module:models/user~User|User} class. */
|
||||
module.exports.User = require("./models/user");
|
||||
/** The {@link module:scheduler~MatrixScheduler|MatrixScheduler} class. */
|
||||
module.exports.MatrixScheduler = require("./scheduler");
|
||||
/** The {@link module:store/session/webstorage~WebStorageSessionStore|
|
||||
* WebStorageSessionStore} class. <strong>Work in progress; unstable.</strong> */
|
||||
module.exports.WebStorageSessionStore = require("./store/session/webstorage");
|
||||
/** True if crypto libraries are being used on this client. */
|
||||
module.exports.CRYPTO_ENABLED = require("./client").CRYPTO_ENABLED;
|
||||
/** {@link module:content-repo|ContentRepo} utility functions. */
|
||||
module.exports.ContentRepo = require("./content-repo");
|
||||
/** The {@link module:filter~Filter|Filter} class. */
|
||||
module.exports.Filter = require("./filter");
|
||||
/** The {@link module:timeline-window~TimelineWindow} class. */
|
||||
module.exports.TimelineWindow = require("./timeline-window").TimelineWindow;
|
||||
/** The {@link module:interactive-auth} class. */
|
||||
module.exports.InteractiveAuth = require("./interactive-auth");
|
||||
/** The {@link module:auto-discovery|AutoDiscovery} class. */
|
||||
module.exports.AutoDiscovery = require("./autodiscovery").AutoDiscovery;
|
||||
import {MemoryCryptoStore} from "./crypto/store/memory-crypto-store";
|
||||
import {MemoryStore} from "./store/memory";
|
||||
import {MatrixScheduler} from "./scheduler";
|
||||
import {MatrixClient} from "./client";
|
||||
|
||||
module.exports.SERVICE_TYPES = require('./service-types').SERVICE_TYPES;
|
||||
|
||||
module.exports.MemoryCryptoStore =
|
||||
require("./crypto/store/memory-crypto-store").default;
|
||||
module.exports.IndexedDBCryptoStore =
|
||||
require("./crypto/store/indexeddb-crypto-store").default;
|
||||
|
||||
/**
|
||||
* Create a new Matrix Call.
|
||||
* @function
|
||||
* @param {module:client.MatrixClient} client The MatrixClient instance to use.
|
||||
* @param {string} roomId The room the call is in.
|
||||
* @return {module:webrtc/call~MatrixCall} The Matrix call or null if the browser
|
||||
* does not support WebRTC.
|
||||
*/
|
||||
module.exports.createNewMatrixCall = require("./webrtc/call").createNewMatrixCall;
|
||||
|
||||
|
||||
/**
|
||||
* Set a preferred audio output device to use for MatrixCalls
|
||||
* @function
|
||||
* @param {string=} deviceId the identifier for the device
|
||||
* undefined treated as unset
|
||||
*/
|
||||
module.exports.setMatrixCallAudioOutput = require('./webrtc/call').setAudioOutput;
|
||||
/**
|
||||
* Set a preferred audio input device to use for MatrixCalls
|
||||
* @function
|
||||
* @param {string=} deviceId the identifier for the device
|
||||
* undefined treated as unset
|
||||
*/
|
||||
module.exports.setMatrixCallAudioInput = require('./webrtc/call').setAudioInput;
|
||||
/**
|
||||
* Set a preferred video input device to use for MatrixCalls
|
||||
* @function
|
||||
* @param {string=} deviceId the identifier for the device
|
||||
* undefined treated as unset
|
||||
*/
|
||||
module.exports.setMatrixCallVideoInput = require('./webrtc/call').setVideoInput;
|
||||
export * from "./client";
|
||||
export * from "./http-api";
|
||||
export * from "./autodiscovery";
|
||||
export * from "./sync-accumulator";
|
||||
export * from "./errors";
|
||||
export * from "./models/event";
|
||||
export * from "./models/room";
|
||||
export * from "./models/group";
|
||||
export * from "./models/event-timeline";
|
||||
export * from "./models/event-timeline-set";
|
||||
export * from "./models/room-member";
|
||||
export * from "./models/room-state";
|
||||
export * from "./models/user";
|
||||
export * from "./scheduler";
|
||||
export * from "./filter";
|
||||
export * from "./timeline-window";
|
||||
export * from "./interactive-auth";
|
||||
export * from "./service-types";
|
||||
export * from "./store/memory";
|
||||
export * from "./store/indexeddb";
|
||||
export * from "./store/session/webstorage";
|
||||
export * from "./crypto/store/memory-crypto-store";
|
||||
export * from "./crypto/store/indexeddb-crypto-store";
|
||||
export * from "./content-repo";
|
||||
export const ContentHelpers = import("./content-helpers");
|
||||
export {
|
||||
createNewMatrixCall,
|
||||
setAudioOutput as setMatrixCallAudioOutput,
|
||||
setAudioInput as setMatrixCallAudioInput,
|
||||
setVideoInput as setMatrixCallVideoInput,
|
||||
} from "./webrtc/call";
|
||||
|
||||
|
||||
// expose the underlying request object so different environments can use
|
||||
// different request libs (e.g. request or browser-request)
|
||||
let request;
|
||||
let requestInstance;
|
||||
|
||||
/**
|
||||
* The function used to perform HTTP requests. Only use this if you want to
|
||||
* use a different HTTP library, e.g. Angular's <code>$http</code>. This should
|
||||
* be set prior to calling {@link createClient}.
|
||||
* @param {requestFunction} r The request function to use.
|
||||
*/
|
||||
module.exports.request = function(r) {
|
||||
request = r;
|
||||
};
|
||||
export function request(r) {
|
||||
requestInstance = r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the currently-set request function.
|
||||
* @return {requestFunction} The current request function.
|
||||
*/
|
||||
module.exports.getRequest = function() {
|
||||
return request;
|
||||
};
|
||||
export function getRequest() {
|
||||
return requestInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply wrapping code around the request function. The wrapper function is
|
||||
@@ -145,15 +82,15 @@ module.exports.getRequest = function() {
|
||||
* previous value, along with the options and callback arguments.
|
||||
* @param {requestWrapperFunction} wrapper The wrapping function.
|
||||
*/
|
||||
module.exports.wrapRequest = function(wrapper) {
|
||||
const origRequest = request;
|
||||
request = function(options, callback) {
|
||||
export function wrapRequest(wrapper) {
|
||||
const origRequest = requestInstance;
|
||||
requestInstance = function(options, callback) {
|
||||
return wrapper(origRequest, options, callback);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
let cryptoStoreFactory = () => new module.exports.MemoryCryptoStore;
|
||||
let cryptoStoreFactory = () => new MemoryCryptoStore;
|
||||
|
||||
/**
|
||||
* Configure a different factory to be used for creating crypto stores
|
||||
@@ -161,9 +98,9 @@ let cryptoStoreFactory = () => new module.exports.MemoryCryptoStore;
|
||||
* @param {Function} fac a function which will return a new
|
||||
* {@link module:crypto.store.base~CryptoStore}.
|
||||
*/
|
||||
module.exports.setCryptoStoreFactory = function(fac) {
|
||||
export function setCryptoStoreFactory(fac) {
|
||||
cryptoStoreFactory = fac;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a Matrix Client. Similar to {@link module:client~MatrixClient}
|
||||
@@ -188,20 +125,20 @@ module.exports.setCryptoStoreFactory = function(fac) {
|
||||
* @see {@link module:client~MatrixClient} for the full list of options for
|
||||
* <code>opts</code>.
|
||||
*/
|
||||
module.exports.createClient = function(opts) {
|
||||
export function createClient(opts) {
|
||||
if (typeof opts === "string") {
|
||||
opts = {
|
||||
"baseUrl": opts,
|
||||
};
|
||||
}
|
||||
opts.request = opts.request || request;
|
||||
opts.store = opts.store || new module.exports.MemoryStore({
|
||||
opts.request = opts.request || requestInstance;
|
||||
opts.store = opts.store || new MemoryStore({
|
||||
localStorage: global.localStorage,
|
||||
});
|
||||
opts.scheduler = opts.scheduler || new module.exports.MatrixScheduler();
|
||||
opts.scheduler = opts.scheduler || new MatrixScheduler();
|
||||
opts.cryptoStore = opts.cryptoStore || cryptoStoreFactory();
|
||||
return new module.exports.MatrixClient(opts);
|
||||
};
|
||||
return new MatrixClient(opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* The request function interface for performing HTTP requests. This matches the
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2019 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.
|
||||
@@ -13,7 +14,6 @@ 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.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* @module models/event-context
|
||||
@@ -33,7 +33,7 @@ limitations under the License.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function EventContext(ourEvent) {
|
||||
export function EventContext(ourEvent) {
|
||||
this._timeline = [ourEvent];
|
||||
this._ourEventIndex = 0;
|
||||
this._paginateTokens = {b: null, f: null};
|
||||
@@ -113,7 +113,3 @@ EventContext.prototype.addEvents = function(events, atStart) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The EventContext class
|
||||
*/
|
||||
module.exports = EventContext;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
Copyright 2019 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.
|
||||
@@ -13,16 +14,17 @@ 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.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* @module models/event-timeline-set
|
||||
*/
|
||||
const EventEmitter = require("events").EventEmitter;
|
||||
const utils = require("../utils");
|
||||
const EventTimeline = require("./event-timeline");
|
||||
|
||||
import {EventEmitter} from "events";
|
||||
import {EventTimeline} from "./event-timeline";
|
||||
import {EventStatus} from "./event";
|
||||
import logger from '../logger';
|
||||
import Relations from './relations';
|
||||
import * as utils from "../utils";
|
||||
import {logger} from '../logger';
|
||||
import {Relations} from './relations';
|
||||
|
||||
// var DEBUG = false;
|
||||
const DEBUG = true;
|
||||
@@ -71,7 +73,7 @@ if (DEBUG) {
|
||||
* via `getRelationsForEvent`.
|
||||
* This feature is currently unstable and the API may change without notice.
|
||||
*/
|
||||
function EventTimelineSet(room, opts) {
|
||||
export function EventTimelineSet(room, opts) {
|
||||
this.room = room;
|
||||
|
||||
this._timelineSupport = Boolean(opts.timelineSupport);
|
||||
@@ -815,11 +817,6 @@ EventTimelineSet.prototype.aggregateRelations = function(event) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The EventTimelineSet class.
|
||||
*/
|
||||
module.exports = EventTimelineSet;
|
||||
|
||||
/**
|
||||
* Fires whenever the timeline in a room is updated.
|
||||
* @event module:client~MatrixClient#"Room.timeline"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user