Compare commits

..

146 Commits

Author SHA1 Message Date
RiotRobot 0dc9c27651 v3.0.0 2020-01-13 12:52:23 +00:00
RiotRobot f6f54c35a3 Prepare changelog for v3.0.0 2020-01-13 12:52:22 +00:00
RiotRobot 786590eadc v3.0.0-rc.1 2020-01-06 13:54:48 +00:00
RiotRobot c9174188ba Prepare changelog for v3.0.0-rc.1 2020-01-06 13:54:47 +00:00
Michael Telatynski 64fb79e0be Merge pull request #1141 from matrix-org/t3chguy/fuzzier_disambiguate
Make displayName disambiguation more fuzzy especially against RTL/LTR content
2020-01-06 13:12:39 +00:00
Michael Telatynski 088ff5d0aa Merge pull request #1129 from matrix-org/t3chguy/m_too_large
stop trying to resend event if we get M_TOO_LARGE
2020-01-06 11:42:27 +00:00
Michael Telatynski 99e58b0297 Make displayName disambiguation more fuzzy especially against RTL/LTR content
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-01-05 19:40:04 +00:00
David Baker f8a1e98de1 Merge pull request #1139 from matrix-org/dbkr/fix_create_key_backup_crosssigning_disabled
Fix creating a key backup with cross signing diabled
2020-01-03 14:52:16 +00:00
David Baker 5487cf2070 Fix callback check
We need to check for getCrossSisgningKey but that was added
unconditionally elsewhere - only add it if we actually have a
getSecretStorageKey callback to use.
2020-01-03 14:36:04 +00:00
J. Ryan Stinnett e998be3a9b Fix typos in comments 2020-01-03 14:21:10 +00:00
David Baker d70767ef3a Merge pull request #1138 from matrix-org/dbkr/key_backup_checkdevicetrust
Use checkDeviceTrust with key backup
2020-01-03 14:03:50 +00:00
David Baker fbb355c5c9 Thank you once again, o great linter, for saving our lines from being too long 2020-01-03 14:02:38 +00:00
David Baker 20bc8071fc Fix creating a key backup with cross signing diabled
It broke if no scret key callback was supplied but a cross-signing
identity did exist (as hopefully explained in comment).

Fixes https://github.com/vector-im/riot-web/issues/11763
2020-01-03 13:52:36 +00:00
David Baker 0438c6c51c Oh great linter, your wisdom knows no bounds. 2020-01-03 13:37:55 +00:00
David Baker b39abba41e Use checkDeviceTrust with key backup
We did check if it was signed with a cross signing key which should
be the norm going forward, but for completeness, use the proper
cross-signing ernabled check for sigs from individual devices too.

Also adds a deviceTrust member to the signature so the app can see
the cross-signing trust status ofthe device without having to
recalculate it.
2020-01-03 13:32:31 +00:00
David Baker fe2bdd027e Merge pull request #1128 from matrix-org/dbkr/keybackup_migrate
Add support for passthrough SSSS secrets
2019-12-19 19:55:39 +00:00
David Baker 320ab050fe Stray p 2019-12-19 17:28:07 +00:00
David Baker 1816d7aa4c comment 2019-12-19 17:27:15 +00:00
David Baker 41b763f331 Just get the private key from the decryption object 2019-12-19 17:25:28 +00:00
Michael Telatynski 36db57615d stop retrying to send event if we get M_TOO_LARGE 2019-12-19 13:21:05 +00:00
David Baker 8f7ed1dc15 Lint 2019-12-19 11:50:25 +00:00
David Baker 83a8a0cf21 Add support for passthrough SSSS secrets
So we can migrate key backup keys

Adding a passthrough secret itself isn't exposed outside of the
js-sdk: hopefully this should only ever be necessary for this
bootstrap process which the js-sdk handles.
2019-12-19 11:23:57 +00:00
J. Ryan Stinnett 9a0de545b8 Merge pull request #1118 from matrix-org/jryans/4s-new-key-backup
Add support for key backups using secret storage
2019-12-12 17:33:05 +00:00
J. Ryan Stinnett 049b769f68 Add docs 2019-12-12 17:27:01 +00:00
J. Ryan Stinnett 1fc2ab7f7d Fix backup tests 2019-12-12 16:06:46 +00:00
J. Ryan Stinnett f2c5b2bd49 Emit user trust via the client 2019-12-12 15:41:56 +00:00
J. Ryan Stinnett f31f88ce31 Merge remote-tracking branch 'origin/develop' into jryans/4s-new-key-backup 2019-12-12 15:38:32 +00:00
J. Ryan Stinnett d35f5152a9 Restore key backup from stored key 2019-12-12 15:11:48 +00:00
J. Ryan Stinnett d8e19db8bf Merge pull request #1117 from matrix-org/jryans/rm-user-verif-event
Remove unused user verification event
2019-12-12 15:06:13 +00:00
J. Ryan Stinnett 376e56d5fd Guard free calls 2019-12-12 14:49:49 +00:00
J. Ryan Stinnett 72f856eca4 Add util to check whether backup key is stored 2019-12-12 14:46:40 +00:00
J. Ryan Stinnett dbab75eae7 Report cross-signing sig as JS style boolean 2019-12-12 13:25:03 +00:00
J. Ryan Stinnett 7457da80e9 Clean up backup trust checks
There were several inaccurate comments and redundant code paths around backup
trust checks.
2019-12-12 13:18:34 +00:00
J. Ryan Stinnett 443e01d38c Always check backup validity, even during enabling
This ensure we run the full backup validity check even when enabling (rather
than assuming we've signed things correctly) to ensure any problem are reported
right away.
2019-12-12 13:15:32 +00:00
J. Ryan Stinnett 880438c5c1 Remove unused user verification event
This was added with cross-signing work, but nothing actually emits it. Let's
remove it until we find a need.
2019-12-12 12:13:40 +00:00
J. Ryan Stinnett 5423d3ca61 Merge pull request #1116 from matrix-org/jryans/4s-new-key-backup
Fix check for private keys
2019-12-11 17:54:19 +00:00
J. Ryan Stinnett 3f448df1d3 Create key backup with secret storage
When secret storage is enable, create a random key for encrypting key backups
and store it in SSSS.
2019-12-11 16:29:02 +00:00
J. Ryan Stinnett a626b44bbe Fix check for private keys
This check for new keys was always true, instead of checking whether something
was added.
2019-12-11 14:58:57 +00:00
J. Ryan Stinnett 4c6e2fca91 Merge pull request #1115 from matrix-org/jryans/restore-watch
Restore watching mode for `start:watch`
2019-12-11 14:58:19 +00:00
J. Ryan Stinnett ab4d9ae4bc Restore watching mode for start:watch
Regressed by #1112
2019-12-11 14:50:28 +00:00
J. Ryan Stinnett fb3d075da2 Merge pull request #1079 from matrix-org/jryans/4s-new-key-backup
Add secret storage bootstrap flow
2019-12-11 11:04:22 +00:00
J. Ryan Stinnett 657e48de7e Fix grammar 2019-12-11 10:51:12 +00:00
J. Ryan Stinnett 1b63cb1406 Merge remote-tracking branch 'origin/develop' into jryans/4s-new-key-backup 2019-12-11 10:09:29 +00:00
Travis Ralston 01f0dd4498 Merge pull request #1112 from matrix-org/travis/babel-7
Part 1 of many: Upgrade to babel@7 and TypeScript
2019-12-10 12:14:24 -07:00
Travis Ralston f59650d8a6 Use better-docs jsdoc template for TypeScript+JS documentation
See description of https://github.com/matrix-org/matrix-js-sdk/pull/1112#issue-351540830 for more info
2019-12-10 11:59:46 -07:00
Travis Ralston 0e444fd925 Re-add babel-eslint
This was accidentally removed.
2019-12-10 11:53:02 -07:00
Travis Ralston 9b8b57d186 Convert randomstring to typescript as a proof of concept 2019-12-10 11:50:01 -07:00
Travis Ralston ca6a52727c Fix logger imports in tests 2019-12-10 11:39:56 -07:00
Travis Ralston 3dfde6bf6a Setup for babel@7 + typescript
Refs:
* https://github.com/matrix-org/matrix-js-sdk/pull/1012
* https://github.com/matrix-org/matrix-js-sdk/pull/1106
2019-12-10 11:31:23 -07:00
J. Ryan Stinnett 780394b051 Merge remote-tracking branch 'origin/develop' into jryans/4s-new-key-backup 2019-12-10 17:54:02 +00:00
J. Ryan Stinnett 6942e3467b Rework to hold cross-signing keys in JS SDK as needed 2019-12-10 17:36:35 +00:00
Michael Telatynski 70eb8a7300 Merge pull request #1100 from matrix-org/t3chguy/remove_bluebird_13
Remove Bluebird: phase 2.5
2019-12-10 16:58:22 +00:00
Michael Telatynski 15a8c23cd0 Merge pull request #1088 from matrix-org/t3chguy/remove_bluebird_12
Remove Bluebird: phase 3
2019-12-10 16:55:23 +00:00
Hubert Chathi 49f0e368d0 Merge pull request #1104 from matrix-org/uhoreg/ignore_verification_done
ignore m.key.verification.done messages when we don't expect any more messages
2019-12-10 09:27:22 -05:00
Bruno Windels 590608a215 Merge pull request #1111 from matrix-org/bwindels/fix-verif-remote-echo-cancel
dont cancel on remote echo of own .request event
2019-12-10 14:09:47 +00:00
Bruno Windels 202fec2a35 dont cancel on remote echo of own .request event 2019-12-10 14:35:17 +01:00
Bruno Windels 817bfa35e5 Merge pull request #1109 from matrix-org/bwindels/accept-verif-request-rebased
Refactor verification request code
2019-12-10 11:10:01 +00:00
Michael Telatynski 110c9800f0 Merge branches 't3chguy/remove_bluebird_12' and 't3chguy/remove_bluebird_13' of github.com:matrix-org/matrix-js-sdk into t3chguy/remove_bluebird_12 2019-12-10 00:19:03 +00:00
Michael Telatynski 1a6dc973bb Merge branches 'develop' and 't3chguy/remove_bluebird_13' of github.com:matrix-org/matrix-js-sdk into t3chguy/remove_bluebird_13 2019-12-10 00:18:32 +00:00
J. Ryan Stinnett 44dd674dab Note about static potential 2019-12-09 17:57:24 +00:00
J. Ryan Stinnett 4a3ce640d7 Document verification methods accepts cross-signing key ID as well 2019-12-09 17:20:54 +00:00
Bruno Windels df6ebf83b4 fix tests 2019-12-09 17:45:01 +01:00
Bruno Windels e5dcc5a407 use verification request with channel from crypto 2019-12-09 17:45:01 +01:00
Bruno Windels 1ee8abb0e6 wrap channel passed to verifier to nofity request
so request is notified when verifier sends an event
2019-12-09 17:45:01 +01:00
Bruno Windels dd40435425 make verifier use channel instead of client straight away
so it is agnostic of the channel used
2019-12-09 17:45:01 +01:00
Bruno Windels 74cb57c761 extract DM verification specific things into InRoomChannel 2019-12-09 17:45:01 +01:00
Bruno Windels 86123f28f7 extract to_device verification specific things into ToDeviceChannel 2019-12-09 17:45:01 +01:00
Bruno Windels f97ab32e7c extract common logic between normal & DM verif into single request class 2019-12-09 17:45:01 +01:00
RiotRobot b0e2544e4b Merge branch 'master' into develop 2019-12-09 11:25:47 +00:00
RiotRobot 0d59963b53 v2.4.6 2019-12-09 11:23:37 +00:00
RiotRobot c669aafedb Prepare changelog for v2.4.6 2019-12-09 11:23:37 +00:00
J. Ryan Stinnett 2a2a40af7a Add separate check for secret storage keys
Decryption vs. signing keys are calculated differently and so require separate
check functions.
2019-12-06 17:51:22 +00:00
J. Ryan Stinnett 1df12d1677 Fix type docs for checkPrivateKey 2019-12-06 14:41:29 +00:00
J. Ryan Stinnett 14a2d7e860 Add docs for more exported cross-signing APIs 2019-12-06 13:20:57 +00:00
J. Ryan Stinnett 3f2c05664f More unstable notices 2019-12-06 13:13:20 +00:00
J. Ryan Stinnett 9b05d1d68e Merge remote-tracking branch 'origin/develop' into jryans/4s-new-key-backup 2019-12-06 12:08:33 +00:00
J. Ryan Stinnett 772d668389 Merge pull request #1105 from matrix-org/jryans/xs-dl-storage
Fix device list's cross-signing storage path
2019-12-06 12:06:49 +00:00
J. Ryan Stinnett 03360a663e Fix device list's cross-signing storage path
Some variables were changed during the course of the initial cross-signing PR
(https://github.com/matrix-org/matrix-js-sdk/pull/832) without updating the
storage path to match, so we weren't storing / loading cross-signing info for
devices in the end.

This updates storage and loading to match where the data now lives in memory.
2019-12-06 11:59:17 +00:00
Hubert Chathi e1e9f690c9 ignore m.key.verification.done messages when we don't expect any more messages 2019-12-05 12:53:59 -05:00
J. Ryan Stinnett 934e81d16c Clarify the key backup integration is unfinished 2019-12-05 16:36:17 +00:00
J. Ryan Stinnett 88bb31d3e6 Expose deriveKey from passphrase 2019-12-05 16:30:10 +00:00
J. Ryan Stinnett 33f5894547 Adjust secret key adding to consume instead of create
This changes `addKey` for secret storage to consume info about a pre-generated
key, rather than creating the key in middle of the method. This eases UI work
that want to have the public and private keys earlier on in the flow.
2019-12-05 16:30:10 +00:00
David Baker fa46d2bef8 Merge pull request #1103 from matrix-org/dbkr/yarn_upgrade_dec19
yarn upgrade
2019-12-05 13:58:50 +00:00
J. Ryan Stinnett 65f8556ee9 Include KDF params in recovery key info
This adjusts the metadata from `createRecoveryKeyFromPassphrase` to include KDF
info formatted in the way secret storage expects. Since
`prepareKeyBackupVersion` did something similar, we adjust it to use the new
function and reshape the objects.
2019-12-05 10:25:24 +00:00
Michael Telatynski ebe174fbef lets *not* get rid of pointless waits :D
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-12-04 23:47:53 +00:00
Michael Telatynski eaaeedbb37 Merge branches 'develop' and 't3chguy/remove_bluebird_12' of github.com:matrix-org/matrix-js-sdk into t3chguy/remove_bluebird_12 2019-12-04 23:44:04 +00:00
Michael Telatynski bf45c176a7 get rid of bunch of seemingly pointless waits
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-12-04 23:42:16 +00:00
Michael Telatynski 87a8e4c216 Apply uhoreg's patch (with jesty stuff changed out)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-12-04 23:37:49 +00:00
Michael Telatynski 30cc7d4f0f Fix one of the crypto.spec.js failures
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-12-04 23:31:20 +00:00
Michael Telatynski 4a47867e49 Down to two test failures 2019-12-04 19:17:58 +00:00
J. Ryan Stinnett 5fced642fa Allow getSecretStorageKey to be async 2019-12-04 17:30:27 +00:00
J. Ryan Stinnett 9fb559307b Add recovery key generation path 2019-12-04 15:32:22 +00:00
J. Ryan Stinnett 96c8c2b9c3 Test for cross-signing private key as well as public 2019-12-04 15:16:38 +00:00
J. Ryan Stinnett 145cdf6985 Remove extra space 2019-12-04 14:59:39 +00:00
J. Ryan Stinnett 5910fd95ff Treat secret as not stored if its key info is missing 2019-12-04 14:23:47 +00:00
J. Ryan Stinnett c0dbf2df7f Publicise method testing for secret storage key existence 2019-12-04 14:23:47 +00:00
David Baker cfaadef669 yarn upgrade 2019-12-04 14:19:38 +00:00
J. Ryan Stinnett eeffe208ec Clarify client-level method for adding secret storage keys 2019-12-04 13:45:56 +00:00
J. Ryan Stinnett 9dc61faa6f Add bootstrap option to specify storage key 2019-12-04 11:36:25 +00:00
J. Ryan Stinnett 2173ab3437 Add test for bootstrapping from scratch 2019-12-04 10:59:28 +00:00
J. Ryan Stinnett 66a6dd1f0c Switch to Node compatible base64 handling 2019-12-03 18:01:45 +00:00
Michael Telatynski 8a8109272a fix undef3
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-12-02 23:45:12 +00:00
Michael Telatynski 7ea30c449e Merge branch 't3chguy/remove_bluebird_13' of github.com:matrix-org/matrix-js-sdk into t3chguy/remove_bluebird_13 2019-12-02 23:43:32 +00:00
Michael Telatynski a6e4096773 fix
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-12-02 23:43:06 +00:00
Michael Telatynski c1e2d646b6 undo remove
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-12-02 23:42:10 +00:00
Michael Telatynski 710ac6847d fix undef2
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-12-02 23:39:47 +00:00
Michael Telatynski f0267eae36 fix undef
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-12-02 23:38:55 +00:00
Michael Telatynski 1632ee3537 fix order
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-12-02 23:18:26 +00:00
Michael Telatynski a16cdb948c Fix cross-signing.spec by waiting for right emit
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-12-02 23:12:45 +00:00
Michael Telatynski c4ae27dae6 Merge branches 'develop' and 't3chguy/remove_bluebird_13' of github.com:matrix-org/matrix-js-sdk into t3chguy/remove_bluebird_13 2019-12-02 22:56:13 +00:00
Michael Telatynski 053bc49738 simplify promiseTry
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-12-02 22:55:05 +00:00
Michael Telatynski 3a1de9fbdc Rip out more bluebirds AGAIN 2019-12-02 22:34:46 +00:00
Michael Telatynski efcaadd0b4 Rip out more bluebirds 2019-12-02 22:34:46 +00:00
Michael Telatynski 0170cb066d add another clean-up to sas.spec.js 2019-12-02 22:34:46 +00:00
Michael Telatynski 6bba5ca25a Rip out some more bluebird 2019-12-02 22:34:46 +00:00
Michael Telatynski edcdeb31ea Rip out bluebird of a bunch more places, not as much to go :| 2019-12-02 22:34:46 +00:00
Michael Telatynski 1286007b2e Rip out bluebird of a bunch of places, lots to go :( 2019-12-02 22:34:46 +00:00
Michael Telatynski 9faab093f7 delint 2019-12-02 22:34:46 +00:00
Michael Telatynski 64bf145e4b Replace rest of defers 2019-12-02 22:34:46 +00:00
Michael Telatynski 733008cfc4 delint and replace more defers 2019-12-02 22:34:46 +00:00
Michael Telatynski bab4582139 Replace more Bluebirdy stuffs 2019-12-02 22:34:46 +00:00
Michael Telatynski fddf2843b4 Replace Bluebird.try 2019-12-02 22:34:45 +00:00
Michael Telatynski f8d83f8273 Stop using Bluebird::mapSeries 2019-12-02 22:34:45 +00:00
J. Ryan Stinnett cfeaf188ed Encode cross-signing keys via base64 2019-12-02 14:39:21 +00:00
J. Ryan Stinnett 58ad1ecbfe Sign secret storage keys when cross-signing keys change 2019-12-02 13:50:43 +00:00
J. Ryan Stinnett 463538178d Clarify purpose of the after local key change helper 2019-12-02 13:02:21 +00:00
J. Ryan Stinnett 14907065d7 Rename device signing auth param 2019-11-29 17:50:59 +00:00
J. Ryan Stinnett ce2059a4b9 Add follow-up steps when restoring from secret storage
If we restore cross-signing keys from secret storage, we also need to run many
of the same follow-up steps from resetting the keys, such as saving to the
crypto store, upgrading device verifications, etc.
2019-11-29 15:20:59 +00:00
J. Ryan Stinnett 2bfc157e64 Clarify cross-signing reset variable 2019-11-29 15:15:18 +00:00
J. Ryan Stinnett fda7a2cf13 Add issue link for key verification 2019-11-29 13:59:09 +00:00
J. Ryan Stinnett e69de8c26f Merge remote-tracking branch 'origin/develop' into jryans/4s-new-key-backup 2019-11-29 11:23:48 +00:00
J. Ryan Stinnett f404c80714 Get cross-signing private keys from secret storage
If you've already set up cross-signing elsewhere and start using a new device,
this loads the private keys from secret storage and regenerates the public keys
to match.

We may also want to download the public keys from the homeserver's key sharing
and verify that they match the private keys, but for now that's left as future
work.
2019-11-29 11:11:45 +00:00
J. Ryan Stinnett e2b680c223 Document CrossSigningInfo#resetKeys 2019-11-28 14:31:53 +00:00
J. Ryan Stinnett bdaf2e3b4f Reflow comment 2019-11-28 12:13:48 +00:00
J. Ryan Stinnett 2190022e64 Add return type 2019-11-28 11:54:20 +00:00
J. Ryan Stinnett e000e2b9fd Move cross-signing storage to 4S into class 2019-11-28 11:54:20 +00:00
J. Ryan Stinnett d5000820fd Fix comment typo 2019-11-27 17:19:41 +00:00
J. Ryan Stinnett 9d91d197e4 Revert to previous cross-signing keys on error 2019-11-27 16:11:06 +00:00
J. Ryan Stinnett 5b767ae948 More comment tweaks to cross-signing keys 2019-11-27 15:51:00 +00:00
David Baker 49448fafaa Correct comment (it's not just a cache) 2019-11-25 15:05:10 +00:00
J. Ryan Stinnett 4116d89d5f Store cross-signing keys in secret storage 2019-11-20 17:48:36 +00:00
J. Ryan Stinnett cc192efe45 Create the SSSS default key when needed 2019-11-20 17:27:57 +00:00
J. Ryan Stinnett feef1a35b9 Add first pass at bootstrapping Secure Secret Storage
This adds a first chunk of bootstrapping Secure Secret Storage by creating
cross-signing keys and supporting interactive auth to upload them.

Part of https://github.com/vector-im/riot-web/issues/11212
2019-11-20 17:23:21 +00:00
J. Ryan Stinnett 55a2f46604 Remove doesCrossSigningHaveKeys, use getId instead 2019-11-20 14:42:46 +00:00
J. Ryan Stinnett ed8b303400 Simplify public key access for cross signing
This simplifies reading the code by removing the semi-magical `[1]` bit to
access the unprefixed version of the key.
2019-11-19 17:47:18 +00:00
J. Ryan Stinnett 4acd06eaba Fix typo in CrossSigning#resetKeys 2019-11-19 13:52:26 +00:00
76 changed files with 4692 additions and 2938 deletions
+18 -12
View File
@@ -1,15 +1,21 @@
{
"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",
"presets": [
["@babel/preset-env", {
"targets": {
"browsers": [
"last 2 versions"
],
"node": 12
},
"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"
]
}
+64
View File
@@ -1,3 +1,67 @@
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)
* No changes since rc.1
Changes in [2.4.6-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v2.4.6-rc.1) (2019-12-04)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.4.5...v2.4.6-rc.1)
+4 -4
View File
@@ -161,7 +161,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) {
...
});
```
@@ -206,7 +206,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);
});
}
@@ -297,7 +297,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 +319,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``
+9 -9
View File
@@ -56,7 +56,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 +70,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 +79,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 +92,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 +116,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 +128,7 @@ rl.on('line', function(line) {
else {
printMessages();
}
}
}
}
rl.prompt();
});
@@ -281,8 +281,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 +295,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(" ");
+23
View File
@@ -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"
}
}
+21 -16
View File
@@ -1,17 +1,17 @@
{
"name": "matrix-js-sdk",
"version": "2.4.6-rc.1",
"version": "3.0.0",
"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",
"gendoc": "jsdoc -c jsdoc.json -P package.json",
"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:watch": "babel src -s -w --skip-initial-build -d lib --verbose --extensions \".ts,.js\"",
"start:init": "babel src -s -d lib --verbose --extensions \".ts,.js\"",
"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",
"build": "babel src -s -d lib --verbose --extensions \".ts,.js\" && 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",
@@ -49,8 +49,6 @@
],
"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,14 +58,20 @@
"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",
"@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",
"babel-eslint": "^10.0.3",
"babel-jest": "^24.9.0",
"better-docs": "^1.4.7",
"browserify": "^16.2.3",
"browserify-shim": "^3.8.13",
"eslint": "^5.12.0",
@@ -75,7 +79,7 @@
"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",
@@ -83,6 +87,7 @@
"source-map-support": "^0.5.13",
"sourceify": "^1.0.0",
"terser": "^4.3.8",
"typescript": "^3.7.3",
"watchify": "^3.11.1"
},
"browserify": {
+1 -2
View File
@@ -24,9 +24,8 @@ 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 logger from '../lib/logger';
/**
* Wrapper for a MockStorageApi, MockHttpBackend and MatrixClient
+1 -3
View File
@@ -15,11 +15,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 logger from '../../lib/logger';
const ROOM_ID = "!room:id";
+1 -2
View File
@@ -31,11 +31,10 @@ import 'source-map-support/register';
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 '../../lib/logger';
let aliTestClient;
const roomId = "!room:localhost";
@@ -4,8 +4,6 @@ const sdk = require("../..");
const HttpBackend = require("matrix-mock-request");
const utils = require("../test-utils");
import Promise from 'bluebird';
describe("MatrixClient events", function() {
const baseUrl = "http://localhost.or.something";
let client;
@@ -162,7 +160,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,11 +1,10 @@
"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 logger from '../../lib/logger';
const baseUrl = "http://localhost.or.something";
const userId = "@alice:localhost";
@@ -356,7 +355,7 @@ describe("MatrixClient event timelines", function() {
.toEqual("start_token");
// expect(tl.getPaginationToken(EventTimeline.FORWARDS))
// .toEqual("s_5_4");
}).done(resolve, reject);
}).then(resolve, reject);
});
});
+5 -5
View File
@@ -181,7 +181,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 +202,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 +220,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 +248,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 +295,7 @@ describe("MatrixClient", function() {
});
}).respond(200, response);
httpBackend.flush().done(function() {
httpBackend.flush().then(function() {
done();
});
});
+7 -9
View File
@@ -5,8 +5,6 @@ const MatrixClient = sdk.MatrixClient;
const HttpBackend = require("matrix-mock-request");
const utils = require("../test-utils");
import Promise from 'bluebird';
describe("MatrixClient opts", function() {
const baseUrl = "http://localhost.or.something";
let client = null;
@@ -86,7 +84,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();
});
@@ -139,7 +137,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 +155,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 +174,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,6 +1,5 @@
"use strict";
import 'source-map-support/register';
import Promise from 'bluebird';
const sdk = require("../..");
const HttpBackend = require("matrix-mock-request");
+10 -12
View File
@@ -5,8 +5,6 @@ const EventStatus = sdk.EventStatus;
const HttpBackend = require("matrix-mock-request");
const utils = require("../test-utils");
import Promise from 'bluebird';
describe("MatrixClient room timelines", function() {
const baseUrl = "http://localhost.or.something";
let client = null;
@@ -151,7 +149,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 +175,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 +208,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 +246,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 +310,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 +348,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 +379,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();
+2 -4
View File
@@ -6,8 +6,6 @@ const utils = require("../test-utils");
const MatrixEvent = sdk.MatrixEvent;
const EventTimeline = sdk.EventTimeline;
import Promise from 'bluebird';
describe("MatrixClient syncing", function() {
const baseUrl = "http://localhost.or.something";
let client = null;
@@ -51,7 +49,7 @@ describe("MatrixClient syncing", function() {
client.startClient();
httpBackend.flushAllExpected().done(function() {
httpBackend.flushAllExpected().then(function() {
done();
});
});
@@ -65,7 +63,7 @@ describe("MatrixClient syncing", function() {
client.startClient();
httpBackend.flushAllExpected().done(function() {
httpBackend.flushAllExpected().then(function() {
done();
});
});
+1 -2
View File
@@ -17,12 +17,11 @@ 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 logger from '../../lib/logger';
const ROOM_ID = "!room:id";
+1 -1
View File
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import logger from '../src/logger';
import logger from '../lib/logger';
// try to load the olm library.
try {
+5 -7
View File
@@ -1,10 +1,8 @@
"use strict";
import Promise from 'bluebird';
// load olm before the sdk if possible
import './olm-loader';
import logger from '../src/logger';
import logger from '../lib/logger';
import sdk from '..';
const MatrixEvent = sdk.MatrixEvent;
@@ -256,7 +254,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 +269,7 @@ HttpResponse.prototype.request = function HttpResponse(
);
}
this.pendingLookup = {
promise: Promise.defer().promise,
promise: new Promise(() => {}),
method: method,
path: path,
};
@@ -308,10 +306,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";
-1
View File
@@ -16,7 +16,6 @@ limitations under the License.
"use strict";
import 'source-map-support/register';
import Promise from 'bluebird';
const sdk = require("../..");
const AutoDiscovery = sdk.AutoDiscovery;
+14 -33
View File
@@ -11,6 +11,7 @@ import TestClient from '../TestClient';
import {MatrixEvent} from '../../lib/models/event';
import Room from '../../lib/models/room';
import olmlib from '../../lib/crypto/olmlib';
import {sleep} from "../../src/utils";
const EventEmitter = require("events").EventEmitter;
@@ -18,8 +19,6 @@ const sdk = require("../..");
const Olm = global.Olm;
jest.useFakeTimers();
describe("Crypto", function() {
if (!sdk.CRYPTO_ENABLED) {
return;
@@ -270,7 +269,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 +301,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 +312,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);
});
});
});
+5 -7
View File
@@ -18,9 +18,7 @@ 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 '../../../lib/logger';
const signedDeviceList = {
"failures": {},
@@ -87,7 +85,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 +104,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 +112,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 +132,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();
+7 -9
View File
@@ -1,7 +1,5 @@
import '../../../olm-loader';
import Promise from 'bluebird';
import sdk from '../../../..';
import algorithms from '../../../../lib/crypto/algorithms';
import MemoryCryptoStore from '../../../../lib/crypto/store/memory-crypto-store.js';
@@ -9,7 +7,7 @@ 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 logger from '../../../../lib/logger';
const MatrixEvent = sdk.MatrixEvent;
const MegolmDecryption = algorithms.DECRYPTION_CLASSES['m.megolm.v1.aes-sha2'];
@@ -56,7 +54,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 +134,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 +280,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': {
+1 -1
View File
@@ -18,7 +18,7 @@ import '../../../olm-loader';
import MemoryCryptoStore from '../../../../lib/crypto/store/memory-crypto-store.js';
import MockStorageApi from '../../../MockStorageApi';
import logger from '../../../../src/logger';
import logger from '../../../../lib/logger';
import OlmDevice from '../../../../lib/crypto/OlmDevice';
import olmlib from '../../../../lib/crypto/olmlib';
+4 -6
View File
@@ -16,8 +16,6 @@ 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';
@@ -27,7 +25,7 @@ import testUtils from '../../test-utils';
import OlmDevice from '../../../lib/crypto/OlmDevice';
import Crypto from '../../../lib/crypto';
import logger from '../../../src/logger';
import logger from '../../../lib/logger';
import olmlib from '../../../lib/crypto/olmlib';
const Olm = global.Olm;
@@ -157,7 +155,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 +336,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 +361,7 @@ describe("MegolmBackup", function() {
resolve();
return Promise.resolve({});
};
await client.createKeyBackupVersion({
client.createKeyBackupVersion({
algorithm: "m.megolm_backup.v1",
auth_data: {
public_key: "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo",
+3
View File
@@ -789,6 +789,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");
+87 -1
View File
@@ -163,7 +163,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 +244,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(crossSigning.isStoredInSecretStorage(secretStorage)).toBeTruthy();
expect(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: request => {
const defaultKeyId = 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(crossSigning.isStoredInSecretStorage(secretStorage)).toBeTruthy();
expect(secretStorage.hasKey()).toBeTruthy();
});
});
+20 -5
View File
@@ -13,7 +13,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.
*/
import logger from '../../../../src/logger';
import logger from '../../../../lib/logger';
try {
global.Olm = require('olm');
@@ -39,7 +39,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 +82,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 +108,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 +141,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();
});
@@ -13,7 +13,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.
*/
import logger from '../../../../src/logger';
import logger from '../../../../lib/logger';
try {
global.Olm = require('olm');
+26 -26
View File
@@ -13,7 +13,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.
*/
import logger from '../../../../src/logger';
import logger from '../../../../lib/logger';
try {
global.Olm = require('olm');
@@ -430,32 +430,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 +473,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 -1
View File
@@ -60,7 +60,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 -2
View File
@@ -17,8 +17,7 @@ limitations under the License.
import sdk from '../..';
const MatrixEvent = sdk.MatrixEvent;
import Promise from 'bluebird';
import logger from '../../src/logger';
import logger from '../../lib/logger';
describe("MatrixEvent", () => {
describe(".attemptDecryption", () => {
+1 -2
View File
@@ -16,13 +16,12 @@ 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 '../../lib/logger';
// Trivial client object to test interactive auth
// (we do not need TestClient here)
+4 -5
View File
@@ -1,10 +1,9 @@
"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 '../../lib/logger';
jest.useFakeTimers();
@@ -83,7 +82,7 @@ describe("MatrixClient", function() {
);
}
pendingLookup = {
promise: Promise.defer().promise,
promise: new Promise(() => {}),
method: method,
path: path,
};
@@ -119,7 +118,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 +170,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(() => {});
});
});
+15 -15
View File
@@ -2,7 +2,7 @@
/* eslint new-cap: "off" */
import 'source-map-support/register';
import Promise from 'bluebird';
import {defer} from '../../src/utils';
const sdk = require("../..");
const MatrixScheduler = sdk.MatrixScheduler;
const MatrixError = sdk.MatrixError;
@@ -14,7 +14,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 +37,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 +47,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 +74,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 +88,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 +101,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 +121,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 +177,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 +298,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 +314,7 @@ describe("MatrixScheduler", function() {
let procCount = 0;
scheduler.setProcessFunction(function(ev) {
procCount += 1;
return defer.promise;
return deferred.promise;
});
expect(procCount).toEqual(0);
});
-1
View File
@@ -1,6 +1,5 @@
"use strict";
import 'source-map-support/register';
import Promise from 'bluebird';
const sdk = require("../..");
const EventTimeline = sdk.EventTimeline;
const TimelineWindow = sdk.TimelineWindow;
-1
View File
@@ -17,7 +17,6 @@ limitations under the License.
/** @module auto-discovery */
import Promise from 'bluebird';
import logger from './logger';
import { URL as NodeURL } from "url";
+200 -73
View File
@@ -26,7 +26,6 @@ import {sleep} from './utils';
* @module client
*/
const EventEmitter = require("events").EventEmitter;
import Promise from 'bluebird';
const url = require('url');
const httpApi = require("./http-api");
@@ -51,14 +50,10 @@ 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 { decodeRecoveryKey } from './crypto/recoverykey';
import { 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 { encodeBase64, decodeBase64 } from '../lib/crypto/olmlib';
const SCROLLBACK_DELAY_MS = 3000;
const CRYPTO_ENABLED = isCryptoAvailable();
@@ -181,7 +176,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 +189,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 +211,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:
@@ -298,7 +294,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;
@@ -677,7 +673,7 @@ MatrixClient.prototype.initCrypto = async function() {
"crypto.warning",
"crypto.devicesUpdated",
"deviceVerificationChanged",
"userVerificationChanged",
"userTrustStatusChanged",
"crossSigning.keysChanged",
]);
@@ -777,7 +773,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 +802,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 +823,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'.
@@ -965,14 +964,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 +1017,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 +1063,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 +1174,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",
]);
/**
@@ -1376,38 +1437,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,13 +1500,19 @@ 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");
}
@@ -1441,11 +1520,15 @@ MatrixClient.prototype.createKeyBackupVersion = async function(info) {
undefined, "POST", "/room_keys/version", undefined, data,
{prefix: httpApi.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;
};
@@ -1549,6 +1632,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 +1653,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,
) {
@@ -1892,7 +2020,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);
@@ -3224,7 +3352,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 +4000,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 +4019,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 +4029,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 +4523,7 @@ MatrixClient.prototype.startClient = async function(opts) {
}
if (this._crypto) {
this._crypto.uploadDeviceKeys().done();
this._crypto.uploadDeviceKeys();
this._crypto.start();
}
@@ -4854,7 +4982,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");
@@ -5354,5 +5482,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)
*/
+67 -15
View File
@@ -20,12 +20,15 @@ limitations under the License.
* @module crypto/CrossSigning
*/
import {pkSign, pkVerify} from './olmlib';
import {pkSign, pkVerify, encodeBase64, decodeBase64} from './olmlib';
import {EventEmitter} from 'events';
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
*/
isStoredInSecretStorage(secretStorage) {
let stored = true;
for (const type of ["master", "self_signing", "user_signing"]) {
stored &= 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);
+6 -6
View File
@@ -23,7 +23,6 @@ limitations under the License.
* Manages the list of other users' devices
*/
import Promise from 'bluebird';
import {EventEmitter} from 'events';
import logger from '../logger';
@@ -31,7 +30,7 @@ import DeviceInfo from './deviceinfo';
import {CrossSigningInfo} from './CrossSigning';
import olmlib from './olmlib';
import IndexedDBCryptoStore from './store/indexeddb-crypto-store';
import {sleep} from '../utils';
import {defer, sleep} from '../utils';
/* State transition diagram for DeviceList._deviceTrackingStatus
@@ -122,7 +121,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 +213,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 +711,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 +776,7 @@ class DeviceListUpdateSerialiser {
}
return prom;
}).done(() => {
}).then(() => {
logger.log('Completed key download for ' + downloadUsers);
this._downloadInProgress = false;
@@ -14,8 +14,6 @@ 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';
+84 -24
View File
@@ -18,8 +18,6 @@ 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';
export const SECRET_STORAGE_ALGORITHM_V1 = "m.secret_storage.v1.curve25519-aes-sha2";
@@ -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();
@@ -132,7 +124,48 @@ 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 = this.getDefaultKeyId()) {
if (!keyId) {
throw new Error("signKey requires a key ID");
}
const keyInfoEvent = this._baseApis.getAccountData(
`m.secret_storage.key.${keyId}`,
);
if (!keyInfoEvent) {
throw new Error(`Key ${keyId} does not exist in account data`);
}
const keyInfo = keyInfoEvent.getContent();
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.
*/
hasKey(keyId = this.getDefaultKeyId()) {
if (!keyId) {
return false;
}
const keyInfo = this._baseApis.getAccountData(
"m.secret_storage.key." + keyId,
);
return keyInfo && keyInfo.getContent();
}
/**
* Store an encrypted secret on the server
@@ -198,6 +231,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.
*
@@ -243,8 +297,13 @@ export default class SecretStorage extends EventEmitter {
// fetch private key from app
[keyId, decryption] = await this._getSecretStorageKey(keys);
// decrypt secret
const encInfo = secretContent.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
switch (keys[keyId].algorithm) {
case SECRET_STORAGE_ALGORITHM_V1:
return decryption.decrypt(
@@ -283,9 +342,12 @@ export default class SecretStorage extends EventEmitter {
// encryption looks sane
for (const keyId of Object.keys(secretContent.encrypted)) {
// get key information from key storage
const keyInfo = this._baseApis.getAccountData(
const keyEvent = this._baseApis.getAccountData(
"m.secret_storage.key." + keyId,
).getContent();
);
if (!keyEvent) return false;
const keyInfo = keyEvent.getContent();
if (!keyInfo) return false;
const encInfo = secretContent.encrypted[keyId];
if (checkKey) {
pkVerify(
@@ -487,9 +549,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");
-2
View File
@@ -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
+2 -3
View File
@@ -22,7 +22,6 @@ limitations under the License.
* @module crypto/algorithms/megolm
*/
import Promise from 'bluebird';
import logger from '../../logger';
const utils = require("../../utils");
@@ -508,7 +507,7 @@ MegolmEncryption.prototype.reshareKeyWithDevice = async function(
userId,
device,
payload,
),
);
await this._baseApis.sendToDevice("m.room.encrypted", {
[userId]: {
@@ -1036,7 +1035,7 @@ MegolmDecryption.prototype.shareKeysWithDevice = function(keyRequest) {
// TODO: retries
return this._baseApis.sendToDevice("m.room.encrypted", contentMap);
});
}).done();
});
};
MegolmDecryption.prototype._buildKeyForwardingMessage = async function(
-3
View File
@@ -20,15 +20,12 @@ limitations under the License.
*
* @module crypto/algorithms/olm
*/
import Promise from 'bluebird';
import logger from '../../logger';
const utils = require("../../utils");
const olmlib = require("../olmlib");
const DeviceInfo = require("../deviceinfo");
const DeviceVerification = DeviceInfo.DeviceVerification;
const base = require("./base");
/**
+455 -584
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -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) {
+20 -3
View File
@@ -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,7 +22,6 @@ limitations under the License.
* Utilities common to olm encryption algorithms
*/
import Promise from 'bluebird';
const anotherjson = require('another-json');
import logger from '../logger';
@@ -303,8 +303,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
*
@@ -399,3 +398,21 @@ module.exports.pkVerify = function(obj, pubkey, userId) {
util.free();
}
};
/**
* Encode a typed array of uint8 as base64.
* @param {Uint8Array} uint8Array The data to encode.
* @return {string} The base64.
*/
module.exports.encodeBase64 = function(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.
*/
module.exports.decodeBase64 = function(base64) {
return Buffer.from(base64, "base64");
};
@@ -15,8 +15,6 @@ 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';
@@ -15,8 +15,6 @@ 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';
@@ -14,8 +14,6 @@ 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';
+1 -3
View File
@@ -15,8 +15,6 @@ 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';
@@ -69,7 +67,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);
+19 -100
View File
@@ -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;
}
}
+10
View File
@@ -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"};
}
}
+24 -10
View File
@@ -205,7 +205,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 +217,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 +254,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 +270,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 +290,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 +338,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 +354,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 +377,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 +407,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(","),
@@ -0,0 +1,233 @@
/*
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 VerificationRequest, {REQUEST_TYPE, START_TYPE} 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 default 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 default 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,249 @@
/*
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 VerificationRequest, {
PHASE_STARTED,
REQUEST_TYPE,
START_TYPE,
CANCEL_TYPE,
} from "./VerificationRequest";
import {
newUnexpectedMessageError,
errorFromEvent,
} from "../Error";
/**
* A key verification channel that sends verification events over to_device messages.
* Generates its own transaction ids.
*/
export default 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 {
newUnknownMethodError,
newUnexpectedMessageError,
errorFromEvent,
errorFactory,
} 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 default 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;
}
}
+3 -4
View File
@@ -19,7 +19,6 @@ limitations under the License.
* 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");
@@ -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;
-2
View File
@@ -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
View File
@@ -18,7 +18,6 @@ limitations under the License.
"use strict";
/** @module interactive-auth */
import Promise from 'bluebird';
const url = require("url");
const utils = require("./utils");
-1
View File
@@ -21,7 +21,6 @@ limitations under the License.
* @module models/event
*/
import Promise from 'bluebird';
import {EventEmitter} from 'events';
import utils from '../utils.js';
import logger from '../logger';
+10 -2
View File
@@ -311,10 +311,18 @@ function calculateDisplayName(selfUserId, displayName, roomState) {
// Next check if the name contains something that look like a mxid
// If it does, it may be someone trying to impersonate someone else
// Show full mxid in this case
// Also show mxid if there are other people with the same or similar
// displayname, after hidden character removal.
let disambiguate = /@.+:.+/.test(displayName);
if (!disambiguate) {
// Also show mxid if the display name contains any LTR/RTL characters as these
// make it very difficult for us to find similar *looking* display names
// E.g "Mark" could be cloned by writing "kraM" but in RTL.
disambiguate = /[\u200E\u200F\u202A-\u202F]/.test(displayName);
}
if (!disambiguate) {
// Also show mxid if there are other people with the same or similar
// displayname, after hidden character removal.
const userIds = roomState.getUserIdsWithDisplayName(displayName);
disambiguate = userIds.some((u) => u !== selfUserId);
}
+2 -1
View File
@@ -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.
@@ -14,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
export function randomString(len) {
export function randomString(len: number): string {
let ret = "";
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+6 -2
View File
@@ -20,7 +20,6 @@ limitations under the License.
* @module scheduler
*/
const utils = require("./utils");
import Promise from 'bluebird';
import logger from './logger';
const DEBUG = false; // set true to enable console logging.
@@ -121,7 +120,7 @@ MatrixScheduler.prototype.queueEvent = function(event) {
if (!this._queues[queueName]) {
this._queues[queueName] = [];
}
const defer = Promise.defer();
const defer = utils.defer();
this._queues[queueName].push({
event: event,
defer: defer,
@@ -157,6 +156,11 @@ MatrixScheduler.RETRY_BACKOFF_RATELIMIT = function(event, attempts, err) {
return -1;
}
// if event that we are trying to send is too large in any way then retrying won't help
if (err.name === "M_TOO_LARGE") {
return -1;
}
if (err.name === "M_LIMIT_EXCEEDED") {
const waitTime = err.data.retry_after_ms;
if (waitTime) {
+6 -7
View File
@@ -15,7 +15,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import Promise from 'bluebird';
import SyncAccumulator from "../sync-accumulator";
import utils from "../utils";
import * as IndexedDBHelpers from "../indexeddb-helpers";
@@ -436,7 +435,7 @@ LocalIndexedDBStoreBackend.prototype = {
*/
_persistSyncData: function(nextBatch, roomsData, groupsData) {
logger.log("Persisting sync data up to ", nextBatch);
return Promise.try(() => {
return utils.promiseTry(() => {
const txn = this.db.transaction(["sync"], "readwrite");
const store = txn.objectStore("sync");
store.put({
@@ -456,7 +455,7 @@ LocalIndexedDBStoreBackend.prototype = {
* @return {Promise} Resolves if the events were persisted.
*/
_persistAccountData: function(accountData) {
return Promise.try(() => {
return utils.promiseTry(() => {
const txn = this.db.transaction(["accountData"], "readwrite");
const store = txn.objectStore("accountData");
for (let i = 0; i < accountData.length; i++) {
@@ -475,7 +474,7 @@ LocalIndexedDBStoreBackend.prototype = {
* @return {Promise} Resolves if the users were persisted.
*/
_persistUserPresenceEvents: function(tuples) {
return Promise.try(() => {
return utils.promiseTry(() => {
const txn = this.db.transaction(["users"], "readwrite");
const store = txn.objectStore("users");
for (const tuple of tuples) {
@@ -495,7 +494,7 @@ LocalIndexedDBStoreBackend.prototype = {
* @return {Promise<Object[]>} A list of presence events in their raw form.
*/
getUserPresenceEvents: function() {
return Promise.try(() => {
return utils.promiseTry(() => {
const txn = this.db.transaction(["users"], "readonly");
const store = txn.objectStore("users");
return selectQuery(store, undefined, (cursor) => {
@@ -512,7 +511,7 @@ LocalIndexedDBStoreBackend.prototype = {
logger.log(
`LocalIndexedDBStoreBackend: loading account data...`,
);
return Promise.try(() => {
return utils.promiseTry(() => {
const txn = this.db.transaction(["accountData"], "readonly");
const store = txn.objectStore("accountData");
return selectQuery(store, undefined, (cursor) => {
@@ -534,7 +533,7 @@ LocalIndexedDBStoreBackend.prototype = {
logger.log(
`LocalIndexedDBStoreBackend: loading sync data...`,
);
return Promise.try(() => {
return utils.promiseTry(() => {
const txn = this.db.transaction(["sync"], "readonly");
const store = txn.objectStore("sync");
return selectQuery(store, undefined, (cursor) => {
-1
View File
@@ -15,7 +15,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import Promise from 'bluebird';
import logger from '../logger';
import {defer} from '../utils';
+1 -2
View File
@@ -15,7 +15,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import Promise from 'bluebird';
import LocalIndexedDBStoreBackend from "./indexeddb-local-backend.js";
import logger from '../logger';
@@ -123,7 +122,7 @@ class IndexedDBStoreWorker {
return;
}
prom.done((ret) => {
prom.then((ret) => {
this.postMessage.call(null, {
command: 'cmd_success',
seq: msg.seq,
-1
View File
@@ -17,7 +17,6 @@ limitations under the License.
/* eslint-disable babel/no-invalid-this */
import Promise from 'bluebird';
import {MemoryStore} from "./memory";
import utils from "../utils";
import {EventEmitter} from 'events';
-1
View File
@@ -22,7 +22,6 @@ limitations under the License.
*/
const utils = require("../utils");
const User = require("../models/user");
import Promise from 'bluebird';
/**
* Construct a new in-memory data store for the Matrix Client.
-1
View File
@@ -16,7 +16,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
"use strict";
import Promise from 'bluebird';
/**
* This is an internal module.
* @module store/stub
+7 -8
View File
@@ -25,7 +25,6 @@ limitations under the License.
* an alternative syncing API, we may want to have a proper syncing interface
* for HTTP and WS at some point.
*/
import Promise from 'bluebird';
const User = require("./models/user");
const Room = require("./models/room");
const Group = require('./models/group');
@@ -360,7 +359,7 @@ SyncApi.prototype._peekPoll = function(peekRoom, token) {
room_id: peekRoom.roomId,
timeout: 30 * 1000,
from: token,
}, undefined, 50 * 1000).done(function(res) {
}, undefined, 50 * 1000).then(function(res) {
if (self._peekRoomId !== peekRoom.roomId) {
debuglog("Stopped peeking in room %s", peekRoom.roomId);
return;
@@ -1150,7 +1149,7 @@ SyncApi.prototype._processSyncResponse = async function(
});
// Handle joins
await Promise.mapSeries(joinRooms, async function(joinObj) {
await utils.promiseMapSeries(joinRooms, async function(joinObj) {
const room = joinObj.room;
const stateEvents = self._mapSyncEventsFormat(joinObj.state, room);
const timelineEvents = self._mapSyncEventsFormat(joinObj.timeline, room);
@@ -1278,8 +1277,8 @@ SyncApi.prototype._processSyncResponse = async function(
}
}
await Promise.mapSeries(stateEvents, processRoomEvent);
await Promise.mapSeries(timelineEvents, processRoomEvent);
await utils.promiseMapSeries(stateEvents, processRoomEvent);
await utils.promiseMapSeries(timelineEvents, processRoomEvent);
ephemeralEvents.forEach(function(e) {
client.emit("event", e);
});
@@ -1383,7 +1382,7 @@ SyncApi.prototype._startKeepAlives = function(delay) {
self._pokeKeepAlive();
}
if (!this._connectionReturnedDefer) {
this._connectionReturnedDefer = Promise.defer();
this._connectionReturnedDefer = utils.defer();
}
return this._connectionReturnedDefer.promise;
};
@@ -1417,7 +1416,7 @@ SyncApi.prototype._pokeKeepAlive = function(connDidFail) {
prefix: '',
localTimeoutMs: 15 * 1000,
},
).done(function() {
).then(function() {
success();
}, function(err) {
if (err.httpStatus == 400 || err.httpStatus == 404) {
@@ -1541,7 +1540,7 @@ SyncApi.prototype._resolveInvites = function(room) {
} else {
promise = client.getProfileInfo(member.userId);
}
promise.done(function(info) {
promise.then(function(info) {
// slightly naughty by doctoring the invite event but this means all
// the code paths remain the same between invite/join display name stuff
// which is a worthy trade-off for some minor pollution.
-1
View File
@@ -17,7 +17,6 @@ limitations under the License.
/** @module timeline-window */
import Promise from 'bluebird';
const EventTimeline = require("./models/event-timeline");
import logger from './logger';
+18 -2
View File
@@ -21,7 +21,6 @@ limitations under the License.
*/
const unhomoglyph = require('unhomoglyph');
import Promise from 'bluebird';
/**
* Encode a dictionary of query parameters.
@@ -676,7 +675,14 @@ module.exports.isNumber = function(value) {
module.exports.removeHiddenChars = function(str) {
return unhomoglyph(str.normalize('NFD').replace(removeHiddenCharsRegex, ''));
};
const removeHiddenCharsRegex = /[\u200B-\u200D\u0300-\u036f\uFEFF\s]/g;
// Regex matching bunch of unicode control characters and otherwise misleading/invisible characters.
// Includes:
// various width spaces U+2000 - U+200D
// LTR and RTL marks U+200E and U+200F
// LTR/RTL and other directional formatting marks U+202A - U+202F
// Combining characters U+0300 - U+036F
// Zero width no-break space (BOM) U+FEFF
const removeHiddenCharsRegex = /[\u2000-\u200F\u202A-\u202F\u0300-\u036f\uFEFF\s]/g;
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -731,3 +737,13 @@ module.exports.defer = () => {
return {resolve, reject, promise};
};
module.exports.promiseMapSeries = async (promises, fn) => {
for (const o of await promises) {
await fn(await o);
}
};
module.exports.promiseTry = (fn) => {
return new Promise((resolve) => resolve(fn()));
};
+2429 -1854
View File
File diff suppressed because it is too large Load Diff