Compare commits

...

2927 Commits

Author SHA1 Message Date
RiotRobot 12b573bc63 v6.2.1 2020-06-05 15:07:04 +01:00
RiotRobot cc8c163e0f Prepare changelog for v6.2.1 2020-06-05 15:07:04 +01:00
Bruno Windels abc7f76679 Merge pull request #1399 from matrix-org/bwindels/backupformatbug-rc
Bring back backup key format migration
2020-06-05 13:32:58 +00:00
Bruno Windels eee04895fe take into account key can be an object now 2020-06-05 15:26:17 +02:00
Bruno Windels f520b88f79 fix type check in migration code 2020-06-05 15:26:17 +02:00
Bruno Windels f0f1c113e4 Revert "remove key backup format migration"
This reverts commit 8d81240c58.
2020-06-05 15:26:17 +02:00
Bruno Windels 84a15761ad Revert "lint"
This reverts commit 9fe0e1e85f.
2020-06-05 15:26:17 +02:00
RiotRobot 30720bfdd7 v6.2.0 2020-06-04 14:57:14 +01:00
RiotRobot 234a18f016 Prepare changelog for v6.2.0 2020-06-04 14:57:14 +01:00
RiotRobot 2c2d531e7f v6.2.0-rc.1 2020-06-02 13:48:12 +01:00
RiotRobot 91e0f7fbc4 Prepare changelog for v6.2.0-rc.1 2020-06-02 13:48:12 +01:00
J. Ryan Stinnett bebeec7d84 Merge pull request #1304 from MTRNord/fix-register-auth-with-new-spec
Make auth argument in the register request compliant with r0.6.0
2020-06-02 10:13:57 +01:00
Travis Ralston 86d448c285 Merge pull request #1393 from matrix-org/travis/uia-3pid
Send the wrong auth params with the right auth params
2020-06-01 09:14:39 -06:00
Travis Ralston 73f8867a6f Send the wrong auth params with the right auth params
Followon from https://github.com/matrix-org/matrix-react-sdk/pull/4667
2020-06-01 09:08:58 -06:00
Hubert Chathi 0d7cb2bf25 Merge pull request #1387 from matrix-org/uhoreg/picklekey
encrypt cached keys with pickle key
2020-05-28 13:51:25 -04:00
Hubert Chathi 991f590056 another unit fix test 2020-05-27 23:32:23 -04:00
Hubert Chathi 0e6758ccbc more unit test fixes 2020-05-27 23:21:15 -04:00
Hubert Chathi 1d9c6520e5 fix unit tests 2020-05-27 20:02:40 -04:00
Hubert Chathi c81f11df0a encrypt/decrypt cached private keys with pickle key 2020-05-27 19:34:28 -04:00
Travis Ralston f39a1e70de Fix the tests 2020-05-27 13:32:28 -06:00
Travis Ralston f0fa249d36 Request fresh flows on the initial registration request 2020-05-27 13:16:10 -06:00
Travis Ralston 8f72197817 Merge branch 'develop' into fix-register-auth-with-new-spec 2020-05-27 12:14:42 -06:00
Hubert Chathi 1556ac84da add a pickle key option to the client 2020-05-27 12:57:00 -04:00
David Baker b6da6bb835 Merge pull request #1385 from Sorunome/patch-1
Fix replying to key share requests
2020-05-26 12:10:44 +01:00
Sorunome b1a882ea79 Fix replying to key share requests
This check was meant to filter out key sharing requests from ourselves. Accidentally, it filtered out sharing requests from anyone *but* ourselves.
2020-05-26 13:04:31 +02:00
Michael Telatynski 3305f2cc72 Merge pull request #1384 from matrix-org/t3chguy/cdn
Add dist to package.json files so CDNs can serve it
2020-05-25 15:56:26 +01:00
Michael Telatynski c589a5211b Add dist to package.json files so CDNs can serve it
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-05-25 14:29:50 +01:00
Michael Telatynski ff0d91979b Merge pull request #1382 from matrix-org/t3chguy/fix_getVersion_warning
Fix getVersion warning saying undefined room
2020-05-22 14:44:11 +01:00
Michael Telatynski 9d6888cf74 Fix getVersion warning saying undefined room
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-05-22 14:21:51 +01:00
David Baker a4a7097c10 Merge pull request #1379 from matrix-org/dbkr/combine_default_rules_processing
Combine the two places we processed client-level default push rules
2020-05-21 15:11:46 +01:00
David Baker cf2b058e7c Update comment on default override rules 2020-05-21 10:06:12 +01:00
Hubert Chathi 048d7a9b63 Merge pull request #1378 from matrix-org/uhoreg/fix_mac_check
make MAC check robust against unpadded vs padded base64 differences
2020-05-20 12:05:35 -04:00
David Baker d6e37d0288 Combine the two places we processed client-level default push rules
We did this in two places: updated existing rules when the rules
were stored and then added new ones every time we computed push
action for an event. Just do both when the rules are saved.
2020-05-20 14:52:37 +01:00
Bruno Windels 3cd562fa96 Merge pull request #1375 from matrix-org/bwindels/remove-keybackup-format-migration
Remove key backup format migration
2020-05-20 08:05:28 +00:00
Michael Telatynski bd569cb041 Merge pull request #1241 from matrix-org/t3chguy/fix_unhomoglyph_import
Add simple browserify browser-matrix.js tests
2020-05-19 11:58:35 +01:00
RiotRobot e3c6a0e1a0 Merge branch 'master' into develop 2020-05-19 11:04:15 +01:00
RiotRobot b29176507c v6.1.0 2020-05-19 11:01:13 +01:00
RiotRobot db25e9719b Prepare changelog for v6.1.0 2020-05-19 11:01:13 +01:00
Michael Telatynski 661901b00d tidy up
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-05-18 20:11:32 +01:00
Michael Telatynski b9352cfcc1 Fix tests
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-05-18 20:03:04 +01:00
Michael Telatynski e381b1901e clean up test
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-05-18 19:39:30 +01:00
Hubert Chathi ebdff505eb make MAC check robust against unpadded vs padded base64 differences 2020-05-15 16:52:53 -04:00
Michael Telatynski f806e4342e Add simple browserify sync test
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-05-15 18:56:09 +01:00
Michael Telatynski 7f32d7d320 revert
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-05-15 16:55:00 +01:00
Michael Telatynski b1b4b21d45 Merge branches 'develop' and 't3chguy/fix_unhomoglyph_import' of github.com:matrix-org/matrix-js-sdk into t3chguy/fix_unhomoglyph_import
 Conflicts:
	src/utils.ts
2020-05-15 16:50:23 +01:00
Hubert Chathi 486e8b5445 Merge pull request #1376 from matrix-org/uhoreg/sas2
support new key agreement method for SAS
2020-05-15 10:59:11 -04:00
Bruno Windels 9fe0e1e85f lint 2020-05-15 13:44:10 +02:00
Bruno Windels 8d81240c58 remove key backup format migration 2020-05-15 12:37:41 +02:00
Hubert Chathi bdadcd4532 support new key agreement method for SAS 2020-05-14 16:14:45 -04:00
RiotRobot 1de9a24677 v6.1.0-rc.1 2020-05-14 16:06:17 +01:00
RiotRobot 6335f14a34 Prepare changelog for v6.1.0-rc.1 2020-05-14 16:06:16 +01:00
Bruno Windels da2ef381ac Merge pull request #1373 from matrix-org/bwindels/remove-asym-4s
Remove support for asymmetric 4S encryption
2020-05-14 08:10:01 +00:00
Bruno Windels 7b173f4c74 don't sign 4S keys anymore 2020-05-13 10:38:03 +02:00
Bruno Windels d49eb0bbc4 remove upgrade path from asym to sym 4S algorithm 2020-05-13 10:38:03 +02:00
Bruno Windels 91556d5bcd fix lint 2020-05-13 10:38:03 +02:00
Bruno Windels 039abe1f75 remove tests for upgrade path 2020-05-12 14:53:50 +02:00
Bruno Windels bc32faf467 don't sign/verify 4s key with cross signing master key anymore 2020-05-12 14:49:20 +02:00
Bruno Windels 74a4dfeb67 remove support for asym 4s enc 2020-05-12 14:29:54 +02:00
RiotRobot f120533fad Merge branch 'master' into develop 2020-05-05 11:01:32 +01:00
RiotRobot a1baf39299 v6.0.0 2020-05-05 10:54:38 +01:00
RiotRobot 9f0f1bcc68 Prepare changelog for v6.0.0 2020-05-05 10:54:37 +01:00
J. Ryan Stinnett 246963e181 Merge pull request #1368 from matrix-org/foldleft/13167-spinner-progress-rc
Add progress callback for key backups
2020-05-04 16:34:38 +01:00
Zoe e3134ab0de satisfy linter 2, now with more self-hatred 2020-05-04 16:25:43 +01:00
Zoe b38d52da72 satisfy the linter 2020-05-04 16:25:43 +01:00
Zoe 411c4f40d9 docs 2020-05-04 16:25:43 +01:00
Zoe 694a85b652 lint 2020-05-04 16:25:43 +01:00
Zoe c84e72f53a Added a progressCallback for backup key loading 2020-05-04 16:25:43 +01:00
David Baker cb7e1a9d82 Merge pull request #1367 from matrix-org/dbkr/increase_olm_second_phase_timeout
Increase timeout for 2nd phase of Olm session creation
2020-05-01 18:32:25 +01:00
David Baker 3c9dfc195e Increase timeout for 2nd phase of Olm session creation
The timeouts on the two phases of olm session creation are 2 and 10
seconds respectively, so sessions will fail if servers take more
than 10s to respond. Now that we have two phases, we can afford to
wait longer on the second one because the user's isn't waiting for
it to finish before the message will send, so increase it to 30s
so servers have more of a chance to respond.
2020-05-01 18:25:25 +01:00
David Baker a5c13041c6 Merge pull request #1366 from matrix-org/dbkr/retry_decryption_logging
Add logging on decryption retries
2020-05-01 17:49:13 +01:00
David Baker d699e98346 Add logging on decryption retries
For https://github.com/vector-im/riot-web/issues/13473
2020-05-01 17:36:49 +01:00
RiotRobot 135a76febd v6.0.0-rc.2 2020-05-01 16:18:21 +01:00
RiotRobot fae2856e7e Prepare changelog for v6.0.0-rc.2 2020-05-01 16:18:21 +01:00
David Baker 98426b6186 Merge pull request #1365 from matrix-org/dbkr/emit_on_store_trusted_master_key_rel
Emit event when a trusted self-key is stored
2020-05-01 15:37:09 +01:00
David Baker a63d9d2ccd lint 2020-05-01 15:28:15 +01:00
David Baker c567139e28 Emit event when a trusted self-key is stored
This will cause our own user trust status to change, so emit an
event to say so.
2020-05-01 15:28:09 +01:00
David Baker 3d495b0753 Merge pull request #1364 from matrix-org/dbkr/emit_on_store_trusted_master_key
Emit event when a trusted self-key is stored
2020-05-01 15:27:36 +01:00
David Baker 4946b55c21 lint 2020-05-01 15:13:02 +01:00
David Baker ad7d7154f4 Emit event when a trusted self-key is stored
This will cause our own user trust status to change, so emit an
event to say so.
2020-05-01 15:01:13 +01:00
Zoe e5b6a9f8cb Merge pull request #1352 from matrix-org/foldleft/12252-large-err
Customize error payload for oversized messages
2020-05-01 09:44:43 +01:00
Zoe 04f27d36ef Update src/crypto/OlmDevice.js
Co-authored-by: Travis Ralston <travpc@gmail.com>
2020-05-01 09:40:11 +01:00
J. Ryan Stinnett 683fc98fdc Merge pull request #1363 from matrix-org/jryans/key-backup-network-fail
Return null for key backup state when we haven't checked yet
2020-04-30 17:21:09 +01:00
J. Ryan Stinnett c303e83444 Return null for key backup state when we haven't checked yet
This allows distinguishing the "unknown" case from "not present".

Part of https://github.com/vector-im/riot-web/issues/13404
2020-04-30 15:40:27 +01:00
J. Ryan Stinnett ae5a40d686 Only consider key backup checked in error if 404
Other error codes are likely to mean we never actually reached the server at
all, so we should not consider that as checked.

Part of https://github.com/vector-im/riot-web/issues/13404
2020-04-30 15:22:50 +01:00
Zoe 409b7068bb Merge pull request #1351 from matrix-org/foldleft/13167-spinner-progress
Added a progressCallback for backup key loading
2020-04-30 14:09:16 +01:00
Zoe f2a12c7154 satisfy linter 2, now with more self-hatred 2020-04-30 14:06:20 +01:00
Zoe efac2eac07 satisfy the linter 2020-04-30 12:04:29 +01:00
RiotRobot f3f6f3e39a v6.0.0-rc.1 2020-04-30 11:14:04 +01:00
RiotRobot f905586dbd Prepare changelog for v6.0.0-rc.1 2020-04-30 11:14:03 +01:00
David Baker f393cea2c2 Merge pull request #1362 from matrix-org/dbkr/sessions_there_on_login_are_old_rel
Add initialFetch param to willUpdateDevices / devicesUpdated
2020-04-29 18:14:13 +01:00
David Baker e937998b40 Add initialFetch param to willUpdateDevices / devicesUpdated
This indicates whether the device database is being populated initially
2020-04-29 17:44:25 +01:00
David Baker f85ec08886 Merge pull request #1360 from matrix-org/dbkr/sessions_there_on_login_are_old
Add initialFetch param to willUpdateDevices / devicesUpdated
2020-04-29 17:42:12 +01:00
J. Ryan Stinnett 7ff68f3844 Merge pull request #1361 from matrix-org/bwindels/fixrequestreadyrace-rc
Fix race between sending .request and receiving .ready over to_device
2020-04-29 17:26:11 +01:00
Bruno Windels 04529bd524 add to_device verif request to txnId -> request map before sending
so if the .ready event arrives before the request finishes,
it still ends up in the correct VerificationRequest
2020-04-29 18:20:16 +02:00
Bruno Windels 2c681d93d9 Merge pull request #1359 from matrix-org/bwindels/fixrequestreadyrace
Fix race between sending .request and receiving .ready over to_device
2020-04-29 16:19:31 +00:00
David Baker 1451fcb040 Add initialFetch param to willUpdateDevices / devicesUpdated
This indicates whether the device database is being populated initially
2020-04-29 17:01:50 +01:00
Zoe 4986f3c2ca docs 2020-04-29 16:55:27 +01:00
Bruno Windels e9edb85a32 add to_device verif request to txnId -> request map before sending
so if the .ready event arrives before the request finishes,
it still ends up in the correct VerificationRequest
2020-04-29 17:51:32 +02:00
J. Ryan Stinnett dca60ae4ae Merge pull request #1358 from matrix-org/bwindels/fixwaitforeventrace-rc
Handle race between sending and await next event from other party
2020-04-29 15:21:35 +01:00
Bruno Windels 025f964b0b Merge pull request #1357 from matrix-org/bwindels/fixwaitforeventrace
Handle race between sending and await next event from other party
2020-04-29 13:53:35 +00:00
Bruno Windels ff46a8fa9e handle race between sending and await next event from other party 2020-04-29 15:52:20 +02:00
Bruno Windels 59412aba51 handle race between sending and await next event from other party 2020-04-29 15:45:51 +02:00
David Baker a351ee9f76 Merge pull request #1356 from matrix-org/dbkr/another_round_of_toast_rel
Add crypto.willUpdateDevices event and make getStoredDevices/getStoredDevicesForUser synchronous
2020-04-29 12:50:00 +01:00
David Baker a5b14092cd Make getStoredDevice calls sync 2020-04-29 12:23:03 +01:00
David Baker 90f1de9c3f Add crypto.willUpdateDevices event and make getStoredDevices/getStoredDevicesForUser synchronous
Add an event fired before devices are updated, so apps can monitor
changes in devices by observing the state before and after.

BREAKING CHANGE:
Make getStoredDevices/getStoredDevicesForUser synchronous so they
can safely be called from the event listener without racing with
the data being updated. They didn't do any async work so didn't need
to be async.

Add doc for crypto.devicesUpdated
2020-04-29 12:23:00 +01:00
David Baker a516f06946 Merge pull request #1354 from matrix-org/dbkr/another_round_of_toast
Add crypto.willUpdateDevices event and make getStoredDevices/getStoredDevicesForUser synchronous
2020-04-29 12:09:11 +01:00
David Baker 7c1f3e4519 Make getStoredDevice calls sync 2020-04-29 11:50:18 +01:00
Michael Telatynski 3bb1a6336b Merge pull request #1350 from matrix-org/t3chguy/redaction_redesign
Fix sender of local echo events in unsigned redactions
2020-04-29 11:17:28 +01:00
J. Ryan Stinnett 7dc926596f Merge pull request #1355 from matrix-org/jryans/rm-extra-key-backup-setup-rc
Remove redundant key backup setup path
2020-04-29 09:46:17 +01:00
J. Ryan Stinnett e775515c38 Remove the other copy instead 2020-04-28 21:34:17 +01:00
J. Ryan Stinnett c116f2b1bc Remove redundant key backup setup path
Bootstrap was create key backup twice due to some code duplication we missed in
previous refactorings.

Fixes https://github.com/vector-im/riot-web/issues/13423
2020-04-28 21:34:17 +01:00
J. Ryan Stinnett f24993d6aa Merge pull request #1353 from matrix-org/jryans/rm-extra-key-backup-setup
Remove redundant key backup setup path
2020-04-28 21:33:49 +01:00
David Baker f053cf1fdb Add crypto.willUpdateDevices event and make getStoredDevices/getStoredDevicesForUser synchronous
Add an event fired before devices are updated, so apps can monitor
changes in devices by observing the state before and after.

BREAKING CHANGE:
Make getStoredDevices/getStoredDevicesForUser synchronous so they
can safely be called from the event listener without racing with
the data being updated. They didn't do any async work so didn't need
to be async.

Add doc for crypto.devicesUpdated
2020-04-28 18:31:04 +01:00
J. Ryan Stinnett 251318eaf0 Remove the other copy instead 2020-04-28 17:20:49 +01:00
J. Ryan Stinnett a66e7d79ad Remove redundant key backup setup path
Bootstrap was create key backup twice due to some code duplication we missed in
previous refactorings.

Fixes https://github.com/vector-im/riot-web/issues/13423
2020-04-28 16:56:37 +01:00
Zoe 38884083a2 Customize error payload for oversized messages 2020-04-28 16:39:35 +01:00
Zoe 0b1088d9a8 lint 2020-04-28 11:31:41 +01:00
Zoe 875f9ca388 Added a progressCallback for backup key loading 2020-04-28 11:28:39 +01:00
David Baker 25cd1f25f1 Merge pull request #1349 from matrix-org/dbkr/retryDecryption_remove_dead_code
Remove some dead code from _retryDecryption
2020-04-28 11:23:53 +01:00
Michael Telatynski 7152ece70a Fix sender of local echo events in unsigned redactions
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-04-27 23:52:22 +01:00
David Baker 896c76ce9d Merge pull request #1348 from matrix-org/dbkr/keyrequest_wait_sync_process
Don't send key requests until after sync processing is finished
2020-04-24 14:38:54 +01:00
David Baker 21d3dd4506 Remove some dead code from _retryDecryption
I think this was attempting to remove the events from _pendingEvents
but a) it wasn't and b) it probably shouldn't be because the retry
itself will re-add them when the decryption attempt starts and remove
them if it succeeds.

Also fix what was presumably a c+p fail.
2020-04-24 14:36:42 +01:00
David Baker 300b32c8d7 Let's keep starting the timer for the retries straight away
rather than leaving them until next time the queue is sent
2020-04-24 13:16:07 +01:00
David Baker 19e3b35fc7 Fix immediate key request sending 2020-04-24 11:26:48 +01:00
David Baker 320811f9ed Don't send key requests until after sync processing is finished
Key requests wait for a short time (500ms) before being sent as
an attempt to wait for the key to arrive before sending a request
for it, but client startup takes way longer than this so this
would still result in key requests being sent for keys that we'd
fetched but were still waiting to be read out of the sync response
and put into the database.
2020-04-24 11:19:52 +01:00
David Baker 991457496f Merge pull request #1346 from matrix-org/dbkr/dont_talk_to_ourself
Prevent attempts to send olm messages to ourselves
2020-04-23 16:10:36 +01:00
RiotRobot 1cb6134758 v5.3.1-rc.4 2020-04-23 15:41:07 +01:00
RiotRobot a255b8e450 Prepare changelog for v5.3.1-rc.4 2020-04-23 15:41:07 +01:00
Bruno Windels 324f036b35 Merge pull request #1347 from matrix-org/bwindels/retry4s-rc
Retry account data upload requests
2020-04-23 14:33:36 +00:00
Bruno Windels 479a284e10 move fn to http-api and add jsdoc 2020-04-23 16:29:08 +02:00
Bruno Windels e0660ce01c log while retrying 2020-04-23 16:29:07 +02:00
Bruno Windels 3a3f6cb7ca cleanup, return straight away 2020-04-23 16:29:07 +02:00
Bruno Windels 9797e6021b exclude aborted requests and matrix errors when creating connectionerror
- throw an AbortError when aborting is detected
 - don't turn AbortError's into a ConnectionError
 - also consider the string "aborted" an AbortError for unit tests
 - unit tests like to reject the request with a MatrixError,
   so exclude those too
2020-04-23 16:29:07 +02:00
Bruno Windels a9212d33b1 retry PUT account data requests 2020-04-23 16:29:07 +02:00
Bruno Windels d32d3fd864 retry util function 2020-04-23 16:29:07 +02:00
Bruno Windels a19cdd06cf throw ConnectionError when request fails 2020-04-23 16:29:07 +02:00
Bruno Windels f4d0f89fda cleanup: use class for MatrixError
this way we get stacktraces as well
2020-04-23 16:29:07 +02:00
Bruno Windels ca34cb951e Merge pull request #1345 from matrix-org/bwindels/retry4s
Retry account data upload requests
2020-04-23 14:28:31 +00:00
David Baker 4c2d1b0385 typo
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2020-04-23 14:40:17 +01:00
Bruno Windels 6b4fefc123 move fn to http-api and add jsdoc 2020-04-23 15:32:40 +02:00
Bruno Windels 86913dccb0 log while retrying 2020-04-23 15:25:10 +02:00
Bruno Windels 30101a4428 cleanup, return straight away 2020-04-23 15:22:46 +02:00
Bruno Windels 668e8f6f24 exclude aborted requests and matrix errors when creating connectionerror
- throw an AbortError when aborting is detected
 - don't turn AbortError's into a ConnectionError
 - also consider the string "aborted" an AbortError for unit tests
 - unit tests like to reject the request with a MatrixError,
   so exclude those too
2020-04-23 15:18:52 +02:00
David Baker 2f51e206c7 Prevent attempts to send olm messages to ourselves
The cause of this I've seen is us sending keyshare reqiests when
we didn't have keys but them arriving when we do, and us replying to
ourseles with keys, so exclude that explicity.

Also catch this lower down to catch any other code paths that
mistaklenly try this. Hopefully the comment explains enough as to why
it won't work.
2020-04-23 13:36:54 +01:00
Bruno Windels 78b8b36a87 retry PUT account data requests 2020-04-23 13:37:56 +02:00
Bruno Windels af3ef86d19 retry util function 2020-04-23 13:37:33 +02:00
Bruno Windels 50febaf477 throw ConnectionError when request fails 2020-04-23 13:37:10 +02:00
Bruno Windels 907f317b04 cleanup: use class for MatrixError
this way we get stacktraces as well
2020-04-23 13:36:40 +02:00
David Baker 6edb78d015 Merge pull request #1344 from matrix-org/dbkr/log_megolm_session_first_index
Log first known index with megolm session updates
2020-04-23 12:30:48 +01:00
David Baker b58846ab6e Log first known index with megolm session updates
Move the logging lower to a point where we know the first index.
2020-04-23 11:43:22 +01:00
David Baker 32233a2c7b Merge pull request #1343 from matrix-org/dbkr/prune_todevice_messages
Prune to_device messages to avoid sending empty messages
2020-04-22 13:54:29 +01:00
David Baker 8b2752441d Prune to_device messages to avoid sending empty messages
Fixes https://github.com/vector-im/riot-web/issues/13322
2020-04-22 13:48:57 +01:00
Michael Telatynski e6af29df94 Merge pull request #1335 from matrix-org/t3chguy/ts1
Convert bunch of things to TypeScript
2020-04-22 10:26:53 +01:00
David Baker 4ae5404fe4 Merge pull request #1342 from matrix-org/dbkr/log_new_olm_sessions
Add logging when making new Olm sessions
2020-04-22 09:47:44 +01:00
David Baker 7f0bdc8ddb Add logging when making new Olm sessions 2020-04-21 18:31:36 +01:00
Bruno Windels 39836f115b Merge pull request #1341 from matrix-org/bwindels/fixfilterlookup-rc
Fix: handle filter not found
2020-04-21 14:15:03 +00:00
Bruno Windels 4a699fe6a7 Merge pull request #1340 from matrix-org/bwindels/fixfilterlookup
Fix: handle filter not found
2020-04-21 14:13:33 +00:00
David Baker 7cc61a887c Merge pull request #1339 from matrix-org/dbkr/getAccountDataFromServer_404_rel
Make getAccountDataFromServer return null if not found
2020-04-21 15:10:20 +01:00
David Baker 8f73f1ed3c Merge pull request #1338 from matrix-org/dbkr/getAccountDataFromServer_404
Make getAccountDataFromServer return null if not found
2020-04-21 15:10:13 +01:00
Bruno Windels 67fb027d39 filter out invalid filter id values like 'undefined'
this way we don't attempt to fetch filters when validating them
with filterId==="undefined"
2020-04-21 16:00:45 +02:00
Bruno Windels c6b61ea0ea Fix unknown filters returning 400 rather than 404
This made getFilter in sync retrying continuously
I also refactored getOrCreateFilter to use await
2020-04-21 16:00:45 +02:00
Bruno Windels 32841234a7 filter out invalid filter id values like 'undefined'
this way we don't attempt to fetch filters when validating them
with filterId==="undefined"
2020-04-21 15:56:39 +02:00
Bruno Windels 6f6520ed0f Fix unknown filters returning 400 rather than 404
This made getFilter in sync retrying continuously
I also refactored getOrCreateFilter to use await
2020-04-21 15:54:50 +02:00
David Baker b3860f3754 Make getAccountDataFromServer return null if not found
Callers assume that if the account data isn't there, the method will
return null rather than throw an exception. This meant that this only
actually worked in these cases when the sync had completed and it was
using the locally stored account data, so this ceased to be a
problem in practice when we made it wait for the initial sync to finish
before entering the security setup flow.

Fixes https://github.com/vector-im/riot-web/issues/13169
2020-04-21 14:32:26 +01:00
David Baker 0958917317 Make getAccountDataFromServer return null if not found
Callers assume that if the account data isn't there, the method will
return null rather than throw an exception. This meant that this only
actually worked in these cases when the sync had completed and it was
using the locally stored account data, so this ceased to be a
problem in practice when we made it wait for the initial sync to finish
before entering the security setup flow.

Fixes https://github.com/vector-im/riot-web/issues/13169
2020-04-21 14:28:10 +01:00
David Baker f42fa7e791 Merge pull request #1337 from matrix-org/dbkr/setdefaultkey_fail_rel
Fix setDefaultKeyId to fail if the request fails
2020-04-21 14:21:42 +01:00
David Baker 7022d1f3bf Merge pull request #1336 from matrix-org/dbkr/setdefaultkey_fail
Fix setDefaultKeyId to fail if the request fails
2020-04-21 14:21:35 +01:00
David Baker b592c41f96 Fix setDefaultKeyId to fail if the request fails
It returned only when the echo came down the sync stream, but we
forgot to make it fail if the reuqest failed and it would just
never return in this case.

Fixes https://github.com/vector-im/riot-web/issues/13162
2020-04-21 13:52:48 +01:00
David Baker 62188571d7 Fix setDefaultKeyId to fail if the request fails
It returned only when the echo came down the sync stream, but we
forgot to make it fail if the reuqest failed and it would just
never return in this case.

Fixes https://github.com/vector-im/riot-web/issues/13162
2020-04-21 13:50:21 +01:00
Michael Telatynski 8cca3392f6 fix localStorage type
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-04-21 12:03:25 +01:00
Michael Telatynski 7082516664 Improve ts types and enable esModuleInterop
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-04-21 11:55:56 +01:00
Michael Telatynski 77a1fc9e60 Convert bunch of things to TypeScript
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-04-21 11:45:54 +01:00
Travis Ralston e2b1dd6532 Merge pull request #1328 from MichaelKohler/issue905-setRoomEncryption-doc
Document setRoomEncryption not modifying room state
2020-04-21 00:30:14 -06:00
Bruno Windels 7b7fdb0a65 Merge pull request #1333 from matrix-org/bwindels/onlyonefilterplease-rc
Fix: don't do extra /filter request when enabling lazy loading of members
2020-04-20 16:36:26 +00:00
Bruno Windels 414279f644 Merge pull request #1332 from matrix-org/bwindels/onlyonefilterplease
Fix: don't do extra /filter request when enabling lazy loading of members
2020-04-20 16:24:52 +00:00
Bruno Windels be91c8580d rename build function 2020-04-20 18:22:30 +02:00
Bruno Windels befaa782f6 when enabling LL, set the flag on Filter, rather than doing /filter 2020-04-20 18:22:30 +02:00
Bruno Windels 8e29dae36a rename build function 2020-04-20 18:06:10 +02:00
Bruno Windels d2438d10a6 when enabling LL, set the flag on Filter, rather than doing /filter 2020-04-20 17:26:19 +02:00
David Baker b3752bb2c6 Merge pull request #1331 from matrix-org/dbkr/fix_reject_no_auth_flow_found_rel
Reject attemptAuth promise if no auth flow found
2020-04-20 13:38:49 +01:00
Michael Kohler 02e145b0de Reject attemptAuth promise if no auth flow found 2020-04-20 13:34:18 +01:00
David Baker 0fcf8777a1 Merge pull request #1329 from MichaelKohler/fix-reject-no-auth-flow-found
Reject attemptAuth promise if no auth flow found
2020-04-20 13:06:44 +01:00
J. Ryan Stinnett 26cae5543b Merge pull request #1330 from matrix-org/dbkr/serialise_prekey_decryptions_rel
Serialise Olm prekey decryptions
2020-04-20 10:14:24 +01:00
David Baker b0573dec77 Add a catch so the error doesn't propagate to later decryptions 2020-04-20 10:08:13 +01:00
David Baker c5f4e762e5 Serialise Olm prekey decryptions
If they overlap, they can both try to create new sessions, but this
removes the OTK so it will only work once.

Fixes https://github.com/vector-im/riot-web/issues/13229
2020-04-20 10:08:07 +01:00
Michael Kohler f4c08477d0 Reject attemptAuth promise if no auth flow found 2020-04-19 14:04:10 +02:00
Michael Kohler 4342ee6d4a Document setRoomEncrption not modifying room state (fixes #905) 2020-04-18 19:49:05 +02:00
Michael Telatynski 7b73561923 Merge pull request #1327 from nchamo/fix-filter-component-check
Fix FilterComponent allowed_values check
2020-04-18 14:45:31 +01:00
Nicolas Chamo 87c0cf233c Update src/filter-component.js
Co-Authored-By: Michael Telatynski <7t3chguy@googlemail.com>
2020-04-17 23:59:38 -03:00
Nicolas Chamo 145cd7894b More lint fixes 2020-04-17 19:56:32 -03:00
Nicolas Chamo 6cf8a76c29 linting 2020-04-17 19:50:15 -03:00
nchamo 7ea09ebe4a Add fix and test
Signed-off-by: Nicolas Chamo <nicolas@chamo.com.ar>
2020-04-17 19:37:33 -03:00
David Baker 1dcb16365b Merge pull request #1326 from matrix-org/dbkr/serialise_prekey_decryptions
Serialise Olm prekey decryptions
2020-04-17 18:07:24 +01:00
David Baker 83b33d9d7a Add a catch so the error doesn't propagate to later decryptions 2020-04-17 18:03:15 +01:00
David Baker 83879fa945 Serialise Olm prekey decryptions
If they overlap, they can both try to create new sessions, but this
removes the OTK so it will only work once.

Fixes https://github.com/vector-im/riot-web/issues/13229
2020-04-17 17:47:46 +01:00
RiotRobot c3903f2796 v5.3.1-rc.3 2020-04-17 17:35:49 +01:00
RiotRobot 18564553ad Prepare changelog for v5.3.1-rc.3 2020-04-17 17:35:48 +01:00
J. Ryan Stinnett fe3e87a9d6 Merge pull request #1323 from matrix-org/dbkr/fix_ssss_reset_rel
Fix cross-signing/SSSS reset
2020-04-17 13:42:47 +01:00
Bruno Windels 78c734d2e9 Merge pull request #1325 from matrix-org/bwindels/fixkeybackupcrash-rc
Fix: crash when backup key needs fixing from corruption issue
2020-04-17 12:23:08 +00:00
Bruno Windels 5afa16d454 method is on crypto 2020-04-17 14:19:55 +02:00
Bruno Windels 57233416d9 Merge pull request #1324 from matrix-org/bwindels/fixkeybackupcrash
Fix: crash when backup key needs fixing from corruption issue
2020-04-17 12:11:07 +00:00
Bruno Windels f56ce29210 method is on crypto 2020-04-17 13:51:12 +02:00
David Baker 149f59e9be lint 2020-04-16 20:42:00 +01:00
David Baker 761892d6b1 Fix cross-signing/SSSS reset
We re-used the old SSSS key even when resetting, meaning we prompted
the user to create a new passphrase but then ignored it and kept using
the old one.

Fixes https://github.com/vector-im/riot-web/issues/13212
2020-04-16 20:41:55 +01:00
David Baker 4b639d5f9a Merge pull request #1322 from matrix-org/dbkr/fix_ssss_reset
Fix cross-signing/SSSS reset
2020-04-16 20:38:47 +01:00
David Baker da8ed77aae lint 2020-04-16 20:18:27 +01:00
David Baker 12f46909f7 Fix cross-signing/SSSS reset
We re-used the old SSSS key even when resetting, meaning we prompted
the user to create a new passphrase but then ignored it and kept using
the old one.

Fixes https://github.com/vector-im/riot-web/issues/13212
2020-04-16 20:13:18 +01:00
RiotRobot faf18b1996 v5.3.1-rc.2 2020-04-16 18:39:06 +01:00
RiotRobot 218deddd00 Prepare changelog for v5.3.1-rc.2 2020-04-16 18:39:06 +01:00
Bruno Windels d6fe650c4c Merge pull request #1321 from matrix-org/bwindels/untrustedqrcodereciprocate-rc
Implement QR code reciprocate for self-verification with untrusted MSK
2020-04-16 12:38:04 +00:00
Bruno Windels 919189db1a Merge pull request #1320 from matrix-org/bwindels/untrustedqrcodereciprocate
Implement QR code reciprocate for self-verification with untrusted MSK
2020-04-16 12:34:46 +00:00
Bruno Windels 951191d99a update comments 2020-04-16 14:31:44 +02:00
Bruno Windels f814a96eac update comments 2020-04-16 14:30:51 +02:00
Bruno Windels 2654f4bf0f implement qr code reciprocate for self-verif with untrusted MSK 2020-04-16 13:26:56 +02:00
Bruno Windels 218aa423c3 implement qr code reciprocate for self-verif with untrusted MSK 2020-04-16 13:01:46 +02:00
RiotRobot 195f3a7550 v5.3.1-rc.1 2020-04-15 19:05:12 +01:00
RiotRobot 1c9343ed8f Prepare changelog for v5.3.1-rc.1 2020-04-15 19:05:11 +01:00
RiotRobot 5c7d9981f8 Merge branch 'release-v5.3.0' into release-v5.3.1 2020-04-15 19:02:40 +01:00
J. Ryan Stinnett 63bc17a6b4 Merge pull request #1319 from matrix-org/jryans/release-script
Adapt release script for riot-desktop
2020-04-15 18:26:04 +01:00
J. Ryan Stinnett 629490c4ae Adapt release script for riot-desktop
This allows the bulk of the release script to be used for riot-desktop, while
skipping the NPM and develop branch parts that do not apply.

Part of https://github.com/vector-im/riot-web/issues/13176
2020-04-15 18:10:46 +01:00
Bruno Windels d59d62f96a Merge pull request #1318 from matrix-org/bwindels/fixindexernotifs
Fix: prevent spurious notifications from indexer
2020-04-15 16:50:18 +00:00
Bruno Windels 8a460c2368 fix jsdoc 2020-04-15 18:33:55 +02:00
Bruno Windels 6f60183316 allow creating event mappers that map events whose events are not reemitted 2020-04-15 17:08:25 +02:00
J. Ryan Stinnett a21c62519b Merge pull request #1317 from matrix-org/jryans/gen-user-obj
Always create our own user object
2020-04-14 16:19:45 +01:00
J. Ryan Stinnett 1b8f6d1739 Always create our own user object
This ensures our own user object is always available for tasks like
verification, even if you aren't in any rooms.

Fixes https://github.com/vector-im/riot-web/issues/13165
2020-04-14 16:02:00 +01:00
Hubert Chathi e087bce61a Merge pull request #1311 from matrix-org/uhoreg/fix_backup_key_format
Fix incorrect backup key format in SSSS
2020-04-13 17:38:45 -04:00
Hubert Chathi d2f24c3e87 cut long line to appease lint 2020-04-13 17:03:17 -04:00
Hubert Chathi 5d606bba66 add test for passthrough on backups 2020-04-13 16:54:02 -04:00
Hubert Chathi 864fe459b7 improve readability of tests 2020-04-13 16:27:46 -04:00
Marcel ea8362ed63 Keep the null in authData if this is the first stage. 2020-04-10 19:32:50 +02:00
Marcel 3a1508c2ab Fix "should make a request if no authdata is provided" test 2020-04-10 19:15:21 +02:00
Marcel f914391aaf Revert hack to set auth = null 2020-04-10 18:31:35 +02:00
Bruno Windels 8992438aa6 Merge pull request #1315 from matrix-org/bwindels/fixe2eecrash
Fix e2ee crash after refreshing after having received a cross-singing key reset
2020-04-10 10:20:20 +00:00
Bruno Windels 197d5ebb44 fix e2ee crash after refreshing after cross-singing key reset 2020-04-10 10:37:12 +02:00
Hubert Chathi 4039498eee use cached backup key if available 2020-04-09 17:05:01 -04:00
Bruno Windels a686231a5b Merge pull request #1314 from matrix-org/bwindels/catchverifysenderrors
Fix: catch send errors in SAS verifier
2020-04-09 16:32:09 +00:00
Bruno Windels 97cf4bff1f fix tests 2020-04-09 18:21:20 +02:00
Bruno Windels 11727833a2 mark request as cancelled immediately after verifier is cancelled
in case send errors prevent us from receiving remote echo
2020-04-09 17:54:45 +02:00
Hubert Chathi df38fde336 apply changes from code review 2020-04-09 11:48:58 -04:00
Bruno Windels 00233d610b pass on send errors so request gets cancelled 2020-04-09 17:23:44 +02:00
Bruno Windels d1c9030fac Merge pull request #1312 from matrix-org/bwindels/fixresetkeys
Clear cross-signing keys when detecting the keys have changed
2020-04-09 13:34:26 +00:00
Bruno Windels 70071eef41 also emit user trust has changed 2020-04-09 14:09:51 +02:00
Bruno Windels 65dd56f53a remove obsolete comment 2020-04-09 13:27:13 +02:00
Bruno Windels c8fb4af369 fix comment style 2020-04-09 13:13:17 +02:00
Bruno Windels 9e1ba992e3 Clear cross-signing keys when detecting the keys have changed 2020-04-09 12:24:29 +02:00
J. Ryan Stinnett bad09fed44 Merge pull request #1310 from matrix-org/jryans/upgrade-2020-04-08
Upgrade deps
2020-04-09 10:19:09 +01:00
Hubert Chathi 5bd146bb85 lint 2020-04-09 00:21:31 -04:00
Hubert Chathi 75703f273f fix unit tests 2020-04-09 00:18:21 -04:00
Hubert Chathi ca7b49d209 fix incorrect backup key format in SSSS 2020-04-09 00:08:17 -04:00
Marcel 379322db0d Revert setting authDict = null by default
Remove console.log
Revert null checks on this._data
2020-04-08 20:50:59 +02:00
J. Ryan Stinnett 30bce8a682 yarn upgrade 2020-04-08 14:41:36 +01:00
RiotRobot f413f0ee5f v5.3.0-rc.1 2020-04-08 13:46:11 +01:00
RiotRobot be6778a931 Prepare changelog for v5.3.0-rc.1 2020-04-08 13:46:10 +01:00
J. Ryan Stinnett 84637c6ebd Merge pull request #1308 from matrix-org/jryans/backup-key-cache-format
Store key backup key in cache as Uint8Array
2020-04-08 11:32:18 +01:00
J. Ryan Stinnett 2ed6e9ba2f Convert recovery key to Uint8Array explicitly 2020-04-08 00:43:52 +01:00
J. Ryan Stinnett 2e3cb54d38 Restore redundant conversion for tests 2020-04-07 19:21:25 +01:00
J. Ryan Stinnett 0efac7eae0 Store key backup key in cache as Uint8Array
Bootstrap was incorrectly storing the key backup key as base64 encoded, when
instead it should be a uint8Array.

Fixes https://github.com/vector-im/riot-web/issues/13057
2020-04-07 18:28:48 +01:00
J. Ryan Stinnett dc56f05d03 Throw error when trying to cache keys in the wrong format 2020-04-07 18:06:57 +01:00
Hubert Chathi 4d2625278c Merge pull request #1307 from MTRNord/fix-keys_query_endpoint
Use the correct request body for the /keys/query endpoint.
2020-04-07 12:01:06 -04:00
Marcel bff8a947a1 Fix expectBobQueryKeys test 2020-04-07 16:53:28 +02:00
Marcel 9cb7406ebd Fix downloadKeys test 2020-04-07 16:44:30 +02:00
Marcel 38681ca6ca Fix expectAliQueryKeys test 2020-04-07 16:31:15 +02:00
Marcel 916696c906 Fix expectKeyQuery test 2020-04-07 16:19:46 +02:00
Marcel 1b3c4c935e Use the correct request body for the /keys/query endpoint.
https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-keys-query
2020-04-07 14:08:49 +02:00
Marcel bf6d1f6555 Fix interactive-auth.js lint 2020-04-07 13:26:27 +02:00
Marcel 9faeac4c83 Merge branch 'develop' into fix-register-auth-with-new-spec 2020-04-07 13:15:51 +02:00
Marcel a863cf3d72 Fix auth linter error and tests 2020-04-07 13:13:49 +02:00
David Baker 66417e6742 Merge pull request #1305 from matrix-org/dbkr/uia_ignore_poll_after_success
Avoid creating two devices on registration
2020-04-07 10:32:08 +01:00
David Baker 6ad4d6dd62 also fix typo 2020-04-07 10:21:57 +01:00
David Baker 59607f8dbf Clear resolve / reject function after rejecting too 2020-04-07 10:20:58 +01:00
David Baker 62380a48d5 Merge pull request #1306 from matrix-org/dbkr/max_warnings_81
Lower max-warnings to 81
2020-04-07 10:19:45 +01:00
David Baker 4245372495 Lower max-warnings to 81
We apparently fixed 11 warnings at some point. Let's keep it that way.
2020-04-07 10:06:23 +01:00
David Baker 964a448470 Avoid creating two devices on registration
Ignore poll requests after a UIA session has finished. Sometimes
the app calls poll() even after the registration has succeeded
which causes the API call to happen again which, in the case of
registration, creates a phantom device.

Fixes https://github.com/vector-im/riot-web/issues/13040
2020-04-06 19:45:18 +01:00
J. Ryan Stinnett 2151f28d4c Merge pull request #1303 from matrix-org/jryans/backup-key-not-cached
Move key backup key creation before caching
2020-04-06 18:41:58 +01:00
Marcel 45a92bcf9c Fix auth check as auth == {} not works 2020-04-06 17:30:32 +02:00
Marcel 0a0441756b Fix auth check 2020-04-06 17:23:24 +02:00
Marcel c1de2ebbf9 Fix authdata to only be null if nothing had been added. 2020-04-06 17:14:43 +02:00
J. Ryan Stinnett 060549656e Move key backup key creation before caching
For some reason, we were trying to cache the key backup key before it was
created, and as a result we wouldn't cache the key until perhaps a second time
through bootstrapping.

Fixes https://github.com/vector-im/riot-web/issues/13023
2020-04-06 16:03:24 +01:00
Marcel 557c2db955 Handle Error during user-interactive auth: TypeError: "this._data is null" 2020-04-06 15:37:52 +02:00
Marcel ed8d064a13 Replace {} with null to be compliant with newer specs. While older also accepted null for the auth part of the registration request object 2020-04-06 15:22:04 +02:00
Zoe 7dabf507a2 Merge pull request #1298 from matrix-org/foldleft/12843-resend-requests
Expose function to force-reset outgoing room key requests
2020-04-06 13:43:06 +01:00
Zoe d69afa47a1 review feedback 2020-04-06 13:30:36 +01:00
Bruno Windels 275a8175aa Merge pull request #1302 from matrix-org/bwindels/selfverifux
Add isSelfVerification property to VerificationRequest
2020-04-03 16:02:05 +00:00
Bruno Windels 40bebf4cbd Merge pull request #1297 from matrix-org/bwindels/qr-reciprocate
QR code reciprocation
2020-04-03 12:28:34 +00:00
Zoe b8bc323c03 add a crypto-level function for resending key requests 2020-04-03 11:47:13 +01:00
Zoe c6d32ea2b0 Expose function to force-reset outgoing room key requests 2020-04-02 16:25:50 +01:00
Bruno Windels 2ace35ad6b add isSelfVerification helper 2020-04-02 17:16:06 +02:00
Bruno Windels b5ea8d3a78 Merge branch 'bwindels/qr-reciprocate' into bwindels/selfverifux 2020-04-02 17:15:32 +02:00
Bruno Windels f3a05f6ed0 fix jsdoc 2020-04-02 13:37:15 +02:00
Bruno Windels 43eae4929b remove ts annotations as I don't want to convert the whole file now 2020-04-02 13:31:17 +02:00
Bruno Windels 6144962c24 cancel with m.user code when user doesn't reciprocate 2020-04-02 12:53:52 +02:00
Bruno Windels 544cc36006 explain a bit why we're doing things here 2020-04-02 11:31:44 +02:00
Hubert Chathi 1834e6688f Merge pull request #1294 from matrix-org/uhoreg/check_sssss_passphrase
Add ability to check symmetric SSSS key before we try to use it
2020-04-01 16:01:34 -04:00
David Baker e1ee56aa43 Merge pull request #1296 from matrix-org/dbkr/debug_stuck_events
Add some debug logging for events stuck to bottom of timeline
2020-04-01 18:54:52 +01:00
David Baker a1779719be Add some debug logging for events stuck to bottom of timeline 2020-04-01 18:48:59 +01:00
Bruno Windels 0e464af627 Merge pull request #1295 from matrix-org/bwindels/fixdoublerequestexplosion
Fix: spontanous verification request cancellation under some circumstances
2020-04-01 17:17:24 +00:00
Bruno Windels fd7f0c3b5a Ignore phase while checking if other party supports scanning
(as the phase hasn't been set yet)

Also, set the qrCodeData in handleEvent as we need it to be async
2020-04-01 19:01:53 +02:00
Bruno Windels ed210a4fb1 Use static constructor that can be async (as looking up device is async)
Also fix static methods not to use this
2020-04-01 19:00:57 +02:00
Bruno Windels c2a0504980 update comments 2020-04-01 18:23:34 +02:00
Bruno Windels b6708871d3 fix olmlib import 2020-04-01 18:23:34 +02:00
Bruno Windels 275ea6aacb spec recommends 11 random bytes for shared secret 2020-04-01 18:23:34 +02:00
Bruno Windels f97d413603 expose chosen method after request has started
so we can show a different ux now that it's not just SAS anymore,
but also reciprocate
2020-04-01 18:23:34 +02:00
Bruno Windels 2c7ea0606b move shared secret to QRCodeData 2020-04-01 18:23:34 +02:00
Bruno Windels d7a7100912 reuse keys from qr code data to sign
so we don't sign the wrong keys if a malicious HS admin
forged a cross-sign identity reset
2020-04-01 18:23:34 +02:00
Bruno Windels 2c54b8d77e ask the user if the other user scanned the QR code 2020-04-01 18:23:34 +02:00
Bruno Windels b30f278e03 checking the userid isn't a thing anymore AFAIK 2020-04-01 18:23:34 +02:00
Bruno Windels b642030a34 keep a copy of the master/device key and mode, for use in reciprocate 2020-04-01 18:23:34 +02:00
Bruno Windels 725976d472 Add QR code buffer generation to VerificationRequest (from react-sdk)
Doing this so later we can keep the other user master signing key on it
so we can make sure we cross-sign that one once we decide to sign.
2020-04-01 18:23:34 +02:00
Bruno Windels 80d87e1bf1 prevent the same event being handled twice in the verification request, result in cancellation 2020-04-01 17:13:24 +02:00
Hubert Chathi d01f527e71 only try to set keyInfo if it exists 2020-03-31 16:54:06 -04:00
Hubert Chathi 3e68c82171 apply changes from code review 2020-03-31 16:34:41 -04:00
Hubert Chathi 51bde23207 Merge branch 'develop' into uhoreg/check_sssss_passphrase 2020-03-31 10:41:39 -04:00
J. Ryan Stinnett 934ed37fdc Merge pull request #1293 from matrix-org/jryans/4s-prompt-overload
Receive private key for caching from the app layer
2020-03-31 11:04:51 +01:00
J. Ryan Stinnett 4a965f5408 Convert secret storage key creation to object 2020-03-31 10:45:02 +01:00
J. Ryan Stinnett 6343da33c3 Merge pull request #1292 from matrix-org/jryans/id-change-red
Track whether we have verified a user before
2020-03-31 10:07:20 +01:00
Hubert Chathi e6edee863f add ability to check symmetric SSSS key before we try to use it 2020-03-30 17:25:03 -04:00
J. Ryan Stinnett 9a1d62438d Receive private key for caching from the app layer
This passes through the new secret storage key as well as the key info so that
bootstrap can use it immediately without triggering an immediate prompt.

Part of https://github.com/vector-im/riot-web/issues/12867
2020-03-30 21:46:22 +01:00
J. Ryan Stinnett d2ba3039c7 Add missing awaits in bootstrap path 2020-03-30 21:46:22 +01:00
J. Ryan Stinnett b6ad4c10bc Track whether we have verified a user before
This tracks whether we have ever cross-signing verified a user before (at least
as far as the current device has ever observed). This info helps to present an
alert in case the user subsequently becomes unverified.

Part of https://github.com/vector-im/riot-web/issues/12808
2020-03-30 14:51:49 +01:00
J. Ryan Stinnett 93954d314e Remove unused variable in device list 2020-03-30 14:51:49 +01:00
J. Ryan Stinnett 282f85f1dd Add missing await when changing key IDs 2020-03-30 14:51:49 +01:00
RiotRobot 223d37ffce Merge branch 'master' into develop 2020-03-30 13:23:17 +01:00
RiotRobot 3d20388ca0 v5.2.0 2020-03-30 13:20:20 +01:00
RiotRobot 198c9d934e Prepare changelog for v5.2.0 2020-03-30 13:20:20 +01:00
J. Ryan Stinnett d43005d91e Merge pull request #1290 from matrix-org/dbkr/send_is_verified_rel
Fix isVerified returning false
2020-03-30 10:28:51 +01:00
Bruno Windels 02264b4572 Merge pull request #1222 from matrix-org/bwindels/fixtestfailure
Fix: error during tests
2020-03-30 08:36:15 +00:00
Bruno Windels add652f18e Merge pull request #1288 from matrix-org/bwindels/alwayssenddone
Send .done event for to_device verification
2020-03-27 16:30:28 +00:00
David Baker 1b9146b9e7 Merge pull request #1291 from matrix-org/dbkr/request_key_backup_key
Request the key backup key & restore backup
2020-03-27 15:43:16 +00:00
J. Ryan Stinnett 5178819b51 Merge pull request #1276 from mjattiot/hotfix/screen_sharing
Make screen sharing works on Chrome using getDisplayMedia()
2020-03-27 15:40:53 +00:00
David Baker f57c25ec27 Make test pass again 2020-03-27 15:13:46 +00:00
David Baker 794429b68b Request the key backup key & restore backup
After a successful verification with ourselves, request the key
backup key too and restore a key backup if we get it.

Also cache the key backup key when we cache the SSK & USK so we have
it available to share.

Fixes https://github.com/vector-im/riot-web/issues/12704
2020-03-27 14:40:15 +00:00
David Baker 983a04bb00 Merge pull request #1289 from matrix-org/dbkr/send_is_verified
Fix isVerified returning false
2020-03-27 14:28:47 +00:00
David Baker adbef16b9d Also pass the parameter in 2020-03-27 14:26:58 +00:00
David Baker 157ea49328 Fix isVerified returning false
which would cause key backups uploads to be missing is_verified
because it was set to `undefined` which would cause the backup to
fail

Fixes https://github.com/vector-im/riot-web/issues/12901
2020-03-27 14:26:53 +00:00
David Baker 17386e7aae Also pass the parameter in 2020-03-27 14:14:45 +00:00
David Baker cb19cd673f Fix isVerified returning false
which would cause key backups uploads to be missing is_verified
because it was set to `undefined` which would cause the backup to
fail

Fixes https://github.com/vector-im/riot-web/issues/12901
2020-03-27 14:11:32 +00:00
Bruno Windels 4f0a297cf3 remove obsolete comment 2020-03-27 12:08:43 +01:00
Bruno Windels 6553e331cd also send .done event for to_device verification 2020-03-27 12:04:48 +01:00
Bruno Windels 21908aea6c Merge pull request #1286 from matrix-org/bwindels/verifignoredupes
Fix: verification gets cancelled when event gets duplicated
2020-03-26 17:07:09 +00:00
David Baker 7c40798ee0 Merge pull request #1287 from matrix-org/dbkr/secret_request_front_door
Use requestSecret on the client to request secrets
2020-03-26 16:16:02 +00:00
David Baker 8cdc635cad lint and aldo indent in a more traditional way 2020-03-26 16:12:11 +00:00
David Baker 7f5ac072e6 Use requestSecret on the client to request secrets
Rather than accessing private method to get the secret storage
object (this was a bit confusing when I grepped for 'requestSecret'
and didn't find anything).
2020-03-26 16:01:35 +00:00
Travis Ralston d69af72c7a Merge pull request #1277 from Awesome-Technologies/develop
Allow guests to fetch TURN servers
2020-03-26 09:33:41 -06:00
Bruno Windels ece1e202de indenting 2020-03-26 15:04:28 +01:00
Bruno Windels 91f38a362d ignore duplicate verification events 2020-03-26 15:04:13 +01:00
RiotRobot 5a3cc314be v5.2.0-rc.1 2020-03-26 12:55:17 +00:00
RiotRobot 3dfaafd177 Prepare changelog for v5.2.0-rc.1 2020-03-26 12:55:16 +00:00
David Baker bdba61975b Merge pull request #1285 from matrix-org/dbkr/trust_cross_signing_flag
Add a flag for whether cross signing signatures are trusted
2020-03-26 12:19:08 +00:00
David Baker 3b9023ec2b add comment 2020-03-26 12:04:16 +00:00
David Baker 4dfc7958b6 lint 2020-03-26 10:07:17 +00:00
David Baker 2fad318726 Make the flag only affect trust of other people's devices 2020-03-26 09:58:05 +00:00
David Baker 480b0e64a6 lint 2020-03-25 18:44:55 +00:00
David Baker 6ec7b5d404 Add a flag for whether cross signing signatures are trusted 2020-03-25 18:36:08 +00:00
J. Ryan Stinnett 0781d78da8 Merge pull request #1282 from matrix-org/jryans/robust-secret-share
Cache user and self signing keys during bootstrap
2020-03-25 17:50:58 +00:00
Zoe 513a256ec1 Merge pull request #1283 from matrix-org/foldleft/remove-extra-promise
remove unnecessary promise
2020-03-25 12:52:42 +00:00
Zoe 9372790666 remove unnecessary promise 2020-03-25 11:47:59 +00:00
J. Ryan Stinnett a6532b7881 Fix logging lints 2020-03-24 18:34:05 +00:00
J. Ryan Stinnett cea3582ed1 Always attempt caching via bootstrap 2020-03-24 18:28:31 +00:00
J. Ryan Stinnett 6bd22a3e9c Add logging to secret request side 2020-03-24 17:44:44 +00:00
J. Ryan Stinnett 7b93b99054 Cache USK and SSK private key during bootstrap 2020-03-24 17:35:59 +00:00
Michael Albert c6eb1525b5 Allow guests to fetch TURN servers
Signed-off-by: Michael Albert <michael.albert@awesome-technologies.de>
2020-03-24 18:04:26 +01:00
J. Ryan Stinnett a4b8ba0bb3 Add logging when replying to secret requests 2020-03-24 15:51:35 +00:00
Zoe 02216b15e5 Merge pull request #1281 from matrix-org/foldleft/12704-key-requests
Functions to cache session backups key automatically
2020-03-24 15:32:09 +00:00
David Baker 42efdf1e0a Merge pull request #1279 from matrix-org/dbkr/unify_cross_signing_checks
Add function for checking cross-signing is ready
2020-03-24 13:34:19 +00:00
David Baker 465f9e634e Merge pull request #1272 from matrix-org/dbkr/symmetric-ssss-migrate
Migration to symmetric SSSS
2020-03-24 13:12:17 +00:00
David Baker 7e92f0e5c8 OK, that really is all the comment formatting 2020-03-24 13:08:49 +00:00
David Baker 859a0d8db2 More comment formatting 2020-03-24 13:08:12 +00:00
David Baker 71740cabb5 comment formatting 2020-03-24 13:06:08 +00:00
David Baker 8f77680750 Typo
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2020-03-24 13:05:15 +00:00
David Baker 509e4b337d Update for new name 2020-03-24 13:01:46 +00:00
David Baker 942ff0c9fd Better name
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2020-03-24 13:00:53 +00:00
David Baker 24c3dd1f1a Merge pull request #1280 from matrix-org/uhoreg/reduce_olm_creation
reduce number of one-time-key requests
2020-03-24 10:30:23 +00:00
Hubert Chathi 4f58e9945b factor out failed device notif to a function, and record all failed devices
instead of filtering out already-notified devices
2020-03-24 00:15:04 -04:00
Hubert Chathi 547ded9155 handle failed devices that we aren't going to retry 2020-03-23 23:14:36 -04:00
Hubert Chathi 4f112e8379 only re-try creating olm sessions for servers that failed to respond
If the server responded, then retrying likely won't help.  Retrying is mainly
to help with slow servers.
2020-03-23 22:36:10 -04:00
Hubert Chathi 4d63f8ed04 don't always do second phase of olm creation
don't need to do the shorter timeout when doing preparation to encrypt, and
skip the second phase if the first phase already took longer than a normal
otk claim
2020-03-23 21:26:56 -04:00
Hubert Chathi 944d39c836 add some comments 2020-03-23 16:51:44 -04:00
Bruno Windels 433977b918 Merge pull request #1275 from matrix-org/bwindels/assumemethodswhentodevice
Fix: assume the requested method is supported by other party with to_device
2020-03-23 19:39:27 +00:00
David Baker d9796e3bec Fix indenting 2020-03-23 19:00:02 +00:00
David Baker 0a7b9109f0 Move aes functions to their own file 2020-03-23 18:56:32 +00:00
David Baker 89bf9ff65b doc style fix 2020-03-23 18:40:53 +00:00
David Baker 7f6e223c0c Add function for checking cross-signing is ready
Aggregate function that checks the various things are in place for
cross-signing to work.
2020-03-23 18:34:16 +00:00
David Baker c696e5238b Merge pull request #1278 from matrix-org/dbkr/blacklist_use_device_trust
Use checkDeviceTrust when computing untrusted devices
2020-03-23 14:58:06 +00:00
David Baker d303fd0c7c Fix test 2020-03-23 14:53:55 +00:00
David Baker e1ad2f8a21 Use checkDeviceTrust when computing untrusted devices
Apparently we missed using cross-signing trust in the js-sdk itself
2020-03-23 14:28:10 +00:00
Zoe 7053cf0182 Functions to cache session backups key automatically 2020-03-23 14:24:35 +00:00
mjattiot e25158975b formatting
Signed-off-by: mjattiot <mjattiot@opensense.fr>
2020-03-20 15:22:56 +01:00
mjattiot 7e028a82fc improved compatibility
Signed-off-by: mjattiot <mjattiot@opensense.fr>
2020-03-20 15:22:56 +01:00
mjattiot 17fe3e4dc1 init
Signed-off-by: mjattiot <mjattiot@opensense.fr>
2020-03-20 15:22:56 +01:00
Bruno Windels 4bd09c45a0 assume the requested method is supported by other party during to_device verification 2020-03-20 13:29:29 +01:00
Zoe 6a7a255081 Merge pull request #1271 from matrix-org/foldleft/12704-key-storage
Rename ssss cache functions to be more general
2020-03-20 11:17:21 +00:00
Zoe 6701fdd486 Rename ssss cache functions to be more general 2020-03-20 10:18:06 +00:00
David Baker ddce14b20b Use the typeof test to avoid undefined 2020-03-19 21:12:57 +00:00
David Baker f1317e824b Don't assume subtleCrypto exists if there's a window
Jest has a window object but doesn't have subtleCrypto
2020-03-19 21:04:36 +00:00
David Baker db285af0b5 Add callback to get the user's current key backup passphrase
And also add a null check
2020-03-19 20:36:00 +00:00
David Baker 0434bf5a48 Add functions to get the raw key backup key 2020-03-19 20:34:57 +00:00
Zoe 78d9111646 Add a store for backup keys 2020-03-19 15:30:28 +00:00
J. Ryan Stinnett 0f28a89c52 Merge pull request #1268 from matrix-org/jryans/send-only-new-key-sigs
Upload only new device signature of master key
2020-03-19 14:56:29 +00:00
Hubert Chathi 92db6599d8 Merge pull request #1270 from matrix-org/uhoreg/expose_prepare_to_encrypt
expose prepareToEncrypt in the client API
2020-03-19 10:56:16 -04:00
Hubert Chathi 70fb5dcaa4 Merge pull request #1269 from matrix-org/uhoreg/device_list_no_dying
don't kill the whole device download if one device gives an error
2020-03-19 10:56:00 -04:00
David Baker a265574da1 Merge remote-tracking branch 'origin/develop' into dbkr/symmetric-ssss-migrate 2020-03-19 14:27:25 +00:00
Hubert Chathi 9911766435 expose prepareToEncrypt in the client API 2020-03-18 18:53:26 -04:00
Hubert Chathi fb08ef9a9b don't kill the whole device download if one device gives an error 2020-03-18 15:28:54 -04:00
J. Ryan Stinnett 2fab06111c Upload only new device signature of master key
This changes bootstrap to only upload the new device signature of the master
key. We were previously _adding_ the new signature, but then uploading both old
and new device key signatures of the master key.

This was particularly bad when re-uploading signatures from deleted devices, as
that would cause the homeserver to reject the entire upload.

Fixes https://github.com/vector-im/riot-web/issues/12752
2020-03-18 18:35:37 +00:00
Bruno Windels 11e3b1ab53 Merge pull request #1267 from matrix-org/bwindels/handleselfverifstartrace
handle racing .start event during self verification
2020-03-18 14:06:45 +00:00
Zoe 3c78f7dbe1 Merge pull request #1266 from matrix-org/foldleft/fix-label-error
A crypto.keySignatureUploadFailure event reported the wrong source
2020-03-18 11:21:16 +00:00
Bruno Windels 999cebc304 handle racing .start event during self verification
by comparing the device id rather than the user id, as defined in the MSC
2020-03-17 17:51:32 +01:00
RiotRobot b2e154377a Merge branch 'master' into develop 2020-03-17 14:09:49 +00:00
RiotRobot d5c68139c0 v5.1.1 2020-03-17 14:07:01 +00:00
RiotRobot cbde77a5cd Prepare changelog for v5.1.1 2020-03-17 14:07:00 +00:00
David Baker 8120041ba7 Merge branch 'symmetric-ssss-migrate' of git://github.com/uhoreg/matrix-js-sdk into uhoreg-symmetric-ssss-migrate 2020-03-17 13:11:01 +00:00
Michael Telatynski 68bc8edaae Merge pull request #1263 from matrix-org/t3chguy/fix_editing
Fix editing of unsent messages by waiting for actual event id
2020-03-17 13:00:55 +00:00
Zoe 7ec339985a a crypto.keySignatureUploadFailure event reported the wrong source 2020-03-17 11:42:03 +00:00
Bruno Windels 70c0abaef8 Merge pull request #1265 from matrix-org/bwindels/fixolmapierror-release
Fix: ensureOlmSessionsForDevices parameter format
2020-03-17 11:25:59 +00:00
Bruno Windels d4dcac93b1 devicesByUser should be userId => array of devices 2020-03-17 12:21:56 +01:00
Michael Telatynski 43889cfb31 use async/await instead
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-03-17 11:14:25 +00:00
Bruno Windels 9e4e14802d Merge pull request #1264 from matrix-org/bwindels/fixolmapierror
Fix: ensureOlmSessionsForDevices parameter format
2020-03-17 11:08:23 +00:00
Bruno Windels 9bebb22746 devicesByUser should be userId => array of devices 2020-03-17 09:51:28 +01:00
Hubert Chathi 3b06b0ffc1 fix lint 2020-03-16 17:22:12 -04:00
Hubert Chathi 1b24d55b24 misc fixes and cleanups 2020-03-16 17:20:54 -04:00
Hubert Chathi c8c6444f6a migrate backup key from asymmetric SSSS to symmetric SSSS 2020-03-16 11:05:07 -04:00
Hubert Chathi 45a88f0517 add function to check that secret storage needs upgrading 2020-03-16 11:00:11 -04:00
Michael Telatynski 53cb3ca79b return the additional promise to simplify the rejection chain
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-03-16 12:23:13 +00:00
Michael Telatynski 68526284f1 fix rejection handling
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-03-16 10:34:39 +00:00
Zoe 68cebc7ff9 If a key upload fails, throw an error and emit an event (#1254) 2020-03-16 10:24:31 +00:00
Michael Telatynski 38286b74e3 tidy up
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-03-16 10:10:22 +00:00
Michael Telatynski 86f56082f0 Make use of scheduler instead of an additional promise
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-03-16 10:09:17 +00:00
Michael Telatynski e87bbfc535 Fix editing of unsent messages by waiting for actual event id
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-03-16 09:29:37 +00:00
Travis Ralston 758e12d6dd Merge pull request #1261 from matrix-org/travis/yarn-cleanup
Remove stuff that yarn install doesn't think we need
2020-03-13 09:25:23 -06:00
Bruno Windels bff461081a Merge pull request #1262 from matrix-org/bwindels/nullcheckonreceipts-release
Fix: prevent error being thrown during sync in some cases
2020-03-13 12:47:52 +00:00
Bruno Windels 33d36395aa check if push actions has a tweaks object 2020-03-13 13:41:32 +01:00
Hubert Chathi e373508211 some fixes in SSSS migration 2020-03-12 18:08:54 -04:00
Bruno Windels 9051edad37 Merge pull request #1258 from matrix-org/bwindels/nullcheckonreceipts
Fix: prevent error being thrown during sync in some cases
2020-03-12 17:09:10 +00:00
Travis Ralston 678b268008 Remove stuff that yarn install doesn't think we need 2020-03-12 10:44:52 -06:00
J. Ryan Stinnett 0361bcf94f Merge pull request #1260 from matrix-org/jryans/verified-to-bool-release
Force `is_verified` for key backups to bool and fix computation
2020-03-12 15:48:07 +00:00
J. Ryan Stinnett b1f02d30c1 Check key backup trust for the right user ID
This corrects the key backup trust computation so that we use the user ID for
the device we're checking inside of always using the client's main user ID,
which would always resulted in false for other people.

Fixes https://github.com/vector-im/riot-web/issues/12693
2020-03-12 15:42:14 +00:00
J. Ryan Stinnett 2af0e5b176 Convert trustedLocally to a bool in all cases
This ensure we always have a boolean value, even when device is null.

Part of https://github.com/vector-im/riot-web/issues/12693
2020-03-12 15:42:14 +00:00
J. Ryan Stinnett c204812d9c Merge pull request #1259 from matrix-org/jryans/verified-to-bool
Force `is_verified` for key backups to bool and fix computation
2020-03-12 15:39:26 +00:00
J. Ryan Stinnett 3b7def880f Check key backup trust for the right user ID
This corrects the key backup trust computation so that we use the user ID for
the device we're checking inside of always using the client's main user ID,
which would always resulted in false for other people.

Fixes https://github.com/vector-im/riot-web/issues/12693
2020-03-12 14:47:28 +00:00
J. Ryan Stinnett e5ec2f03c2 Convert trustedLocally to a bool in all cases
This ensure we always have a boolean value, even when device is null.

Part of https://github.com/vector-im/riot-web/issues/12693
2020-03-12 14:21:46 +00:00
Bruno Windels a1b3e8055f check if push actions has a tweaks object 2020-03-12 12:59:43 +01:00
Bruno Windels 1e503261f2 Merge pull request #1257 from matrix-org/bwindels/devicelegacyverif
Add a method for legacy single device verification, returning a verification request
2020-03-12 11:30:08 +00:00
David Baker 9107a3e569 Merge pull request #1256 from matrix-org/dbkr/yarn_upgrade_20200311
yarn upgrade
2020-03-12 09:44:51 +00:00
RiotRobot c6070519ed v5.1.1-rc.1 2020-03-11 15:05:49 +00:00
RiotRobot 30ece1be70 Prepare changelog for v5.1.1-rc.1 2020-03-11 15:05:48 +00:00
Bruno Windels b66a1d30a0 method for legacy single device verification, returning a verification request rather than a verifier 2020-03-11 15:53:38 +01:00
David Baker 51e1f56873 yarn upgrade 2020-03-11 14:47:48 +00:00
Hubert Chathi 86304fd037 Merge pull request #1252 from matrix-org/uhoreg/megolm_speed
refactor megolm encryption to improve perceived speed
2020-03-10 20:09:41 -04:00
Hubert Chathi 04387e78cc some cleanups 2020-03-10 15:56:33 -04:00
Travis Ralston 2bfc44b947 Merge pull request #1253 from matrix-org/travis/remove-v1-identity
Remove v1 identity server fallbacks
2020-03-10 09:30:22 -06:00
Bruno Windels 33941eb37b Merge pull request #1251 from matrix-org/bwindels/altaliasesforname
Use alt_aliases instead of local ones for room names
2020-03-10 12:42:50 +00:00
J. Ryan Stinnett 0a45559276 Merge pull request #1250 from matrix-org/jryans/xsign-slow-login
Upload cross-signing key signatures in the background
2020-03-10 11:07:45 +00:00
Travis Ralston 800441e0ed Appease the linter 2020-03-09 17:10:37 -06:00
Travis Ralston 95164d08d5 Remove v1 identity server fallbacks
Fixes https://github.com/vector-im/riot-web/issues/10443

**Review with https://github.com/matrix-org/matrix-react-sdk/pull/4191**
2020-03-09 17:06:10 -06:00
Hubert Chathi 98d955ef1f refactor megolm encryption to improve perceived speed
- allow applications to pre-send decryption keys before the message is sent
- establish new olm sessions with a shorter timeout first, and then re-try in
  the background with a longer timeout without blocking message sending
2020-03-09 18:38:18 -04:00
Bruno Windels 950dadc14e fix tests 2020-03-09 18:33:20 +01:00
Bruno Windels 31d2f0135b use alt aliases instead of local ones for room names 2020-03-09 17:13:50 +01:00
J. Ryan Stinnett c02928f294 Upload cross-signing key signatures in the background
At the moment, uploading cross-signing key signatures is a slow process that can
potentially take many minutes (!) for large accounts / slow servers. This
changes to do the bootstrapping related versions of this in the background.

Note that key signature uploads for interactive flows like verification are
still blocking for now.

Fixes https://github.com/vector-im/riot-web/issues/12223
2020-03-09 15:08:14 +00:00
J. Ryan Stinnett 951fff45e6 Skip device verif upgrades when callback not present
This skips the upgrade when the upgrade callback is not present (which is
expected as no one sets it currently). This adds logging for around the upgrade
process.
2020-03-09 15:03:02 +00:00
J. Ryan Stinnett 4fdd817ff5 Add logging around key change post-processing 2020-03-09 14:46:10 +00:00
J. Ryan Stinnett acba31bd6d Merge pull request #1249 from matrix-org/jryans/sharing-names
Fix secret sharing names to match spec
2020-03-09 13:48:06 +00:00
J. Ryan Stinnett b5eea01848 Fix secret sharing names to match spec
When sharing keys, we should use `m.cross_signing` prefix.

Part of https://github.com/vector-im/riot-web/issues/12661
2020-03-09 13:40:02 +00:00
Bruno Windels 074e02ccf2 Merge pull request #1248 from matrix-org/bwindels/removecryptoverifstartevent
Cleanup: remove crypto.verification.start event
2020-03-06 16:47:34 +00:00
Bruno Windels 4b9bc67cb6 remove crypto.verification.start event
as it is not used anymore by the react-sdk
2020-03-06 16:48:44 +01:00
Zoe 936ef4116b For self-verifications, also request keys from the other device (#1245)
* For self-verifications, also request keys from the other device
* removed some XXX's so the editor doesn't think it's three issues
* add methods to access key cache callbacks
2020-03-06 09:56:56 +00:00
J. Ryan Stinnett 9883d6851a Merge pull request #1246 from matrix-org/jryans/xsign-trust-bool
Fix regression in key backup request params
2020-03-05 14:16:16 +00:00
J. Ryan Stinnett 4c08e126ca Fix regression in key backup request params
This converts the cross-signing trust to a boolean as required by the
homeserver.

Regressed by https://github.com/vector-im/riot-web/issues/12599
Fixes https://github.com/vector-im/riot-web/issues/12618
2020-03-05 12:17:42 +00:00
J. Ryan Stinnett bc53f8fdec Merge pull request #1244 from matrix-org/jryans/xsign-key-backup-verif
Use cross-signing trust to mark backups verified
2020-03-03 18:03:46 +00:00
J. Ryan Stinnett 0b76d3d7bd Merge pull request #1243 from matrix-org/jryans/xsign-auto-share
Check both cross-signing and local trust for key sharing
2020-03-03 18:03:38 +00:00
J. Ryan Stinnett abaf71418e Use cross-signing trust to mark backups verified
This changes to cross-signing trust as well as local trust when we decide
whether to tell the homeserver a session of room keys is verified.

Fixes https://github.com/vector-im/riot-web/issues/12599
2020-03-03 15:52:38 +00:00
J. Ryan Stinnett c96a906b39 Check both cross-signing and local trust for key sharing
When sharing room keys with our own devices, this ensure we check both
cross-signing and local trust.

Fixes https://github.com/vector-im/riot-web/issues/12596
2020-03-03 15:12:40 +00:00
RiotRobot da96765020 Merge branch 'master' into develop 2020-03-02 16:55:55 +00:00
RiotRobot f654c8a892 v5.1.0 2020-03-02 16:53:10 +00:00
RiotRobot 336fce55df Prepare changelog for v5.1.0 2020-03-02 16:53:10 +00:00
Zoe d11946d86b Merge pull request #1242 from matrix-org/foldleft/fix-bad-merge
Fixed up tests to match new way that crypto stores are created
2020-03-02 15:01:27 +00:00
Zoe 3a4c72ac08 actually, returning is unnecessary 2020-03-02 14:46:26 +00:00
Zoe 6d3f0f653b there's some days that the linter and i, we just really don't see eye-to-eye 2020-03-02 14:38:24 +00:00
Zoe 81d3534569 added return back 2020-03-02 13:06:13 +00:00
Zoe c54922dba3 Fixed up tests to match new way that crypto stores are created 2020-03-02 12:51:47 +00:00
Michael Telatynski 9da1f7b8d5 Fix unhomoglyph import to make browser-matrix.js happy once more 2020-03-02 10:54:14 +00:00
Zoe a4ed3d97fc Merge pull request #1235 from matrix-org/foldleft/12299-local-ssk
Store USK and SSK locally
2020-03-02 09:52:44 +00:00
Zoe 656694ee00 proper spacing for test output text 2020-03-02 09:45:55 +00:00
Hubert Chathi c6b5936f8a use the right operator 2020-02-28 16:09:24 -05:00
Travis Ralston 03752ab60c Merge pull request #1236 from matrix-org/travis/unpadded-qr-codes
Use unpadded base64 for QR code secrets
2020-02-28 10:20:57 -07:00
Bruno Windels 7203542cfd Merge pull request #1239 from matrix-org/bwindels/dontrequiredoneforselfverif
Don't require .done event for finishing self-verification
2020-02-28 15:16:09 +00:00
Bruno Windels 4b36bbc122 Merge pull request #1237 from matrix-org/bwindels/dontcancelas3rdparty
Don't cancel as 3rd party in verification request
2020-02-28 15:15:49 +00:00
Bruno Windels ecaf21ceb0 Don't require .done event for finishing self-verification
Instead, call onVerifierFinished from the verifier on the request
so we can internally mark it as done. This flag is not persisted,
but we don't have historical (persisted) to-device requests anyway.
2020-02-28 14:56:38 +01:00
Zoe 67fe4e1460 lint & only cache valid keys 2020-02-28 11:04:28 +00:00
Zoe a94503ad03 address PR feedback 2020-02-28 10:43:57 +00:00
Bruno Windels ce6dd8688c Merge pull request #1234 from matrix-org/bwindels/evenmoreloggingforverif
Verification: log when switching start event
2020-02-28 10:24:09 +00:00
Hubert Chathi 1151bdc6db initial work in migrating ssss to symmetric 2020-02-27 22:56:34 -05:00
Hubert Chathi ed223d1d76 remove unnecessary awaits 2020-02-27 22:54:43 -05:00
Bruno Windels 650eee7705 dont cancel as 3rd party in verification request 2020-02-27 18:38:16 +01:00
Travis Ralston 4510eb6540 Match all the equals
Co-Authored-By: Hubert Chathi <hubert@uhoreg.ca>
2020-02-27 10:10:24 -07:00
Travis Ralston 9a236f317d Use unpadded base64 for QR code secrets 2020-02-27 10:00:56 -07:00
Zoe 25c467d608 Wire cache through to matrix client 2020-02-27 16:53:26 +00:00
Zoe c2daf0d74e Store data in cryptostore 2020-02-27 16:53:26 +00:00
J. Ryan Stinnett fa19616ad1 Merge pull request #1233 from matrix-org/jryans/safari-e2e-idb
Perform crypto store operations directly after transaction
2020-02-27 16:48:09 +00:00
Zoe 02cbd33284 Added cache callbacks to CrossSigningInfo 2020-02-27 16:37:25 +00:00
Zoe 941ae18d74 Added tests for CrossSigningInfo.getCrossSigningKey 2020-02-27 16:37:25 +00:00
Bruno Windels 90f400abe1 log when switching start event 2020-02-27 17:35:58 +01:00
J. Ryan Stinnett ff2d93d421 Perform crypto store operations directly after transaction
At least on Safari but perhaps other browsers as well, you must perform
IndexedDB operations in the same JS task as you start the transaction. As a
concrete example, you cannot open the transaction and await some promise before
actually using it.

This fixes the crypto store to meet this requirement.

Fixes https://github.com/vector-im/riot-web/issues/12207
2020-02-27 14:57:07 +00:00
Bruno Windels 8d26bd9a17 Merge pull request #1232 from matrix-org/bwindels/logeventidinverifreq
More verification request logging
2020-02-27 13:26:53 +00:00
J. Ryan Stinnett a9fa0484ff Add exception handling to crypto store paths
A few of the crypto store backend paths were missing try / catch wrappers to
abort the transaction if the inner callback throws.
2020-02-27 12:26:18 +00:00
J. Ryan Stinnett d3d12ab62f Merge pull request #1231 from matrix-org/jryans/upgrade-deps-2020-02-26
Upgrade deps
2020-02-27 11:24:33 +00:00
Bruno Windels 1e29b1a31d log event id in verif request to differentiate between double processing vs double sending 2020-02-26 18:49:18 +01:00
J. Ryan Stinnett 9318bf5f2f Upgrade deps 2020-02-26 15:00:43 +00:00
RiotRobot 6b35302442 v5.1.0-rc.1 2020-02-26 14:16:57 +00:00
RiotRobot 2937e58215 Prepare changelog for v5.1.0-rc.1 2020-02-26 14:16:57 +00:00
J. Ryan Stinnett d42589b6cc Merge pull request #1230 from matrix-org/jryans/dist-tags
Add latest dist-tag for releases
2020-02-26 14:14:09 +00:00
J. Ryan Stinnett 26e9dfb4fb Add latest dist-tag for a release 2020-02-26 14:07:20 +00:00
J. Ryan Stinnett f27d03a6bc Always publish to next tag
This ensures that anyone who wants the latest version (pre-release or final
release) can always use the `next` tag.
2020-02-26 13:55:46 +00:00
J. Ryan Stinnett b1e3150a81 Reset device list dirty flag only after writing
This ensures we wait until after the device list writes to the crypto store
before marking thing as clean. This is particularly important for the error
path, as the write to the crypto store can fail.

Part of https://github.com/vector-im/riot-web/issues/12207
2020-02-25 17:56:47 +00:00
Hubert Chathi 5d52053caa use symmetric encryption for SSSS 2020-02-24 17:38:53 -05:00
Bruno Windels ce668d051c Merge pull request #1225 from matrix-org/bwindels/aliasautocomplete
Add room method for alt_aliases
2020-02-24 12:17:45 +00:00
David Baker e06579ecf5 Merge pull request #1227 from matrix-org/dbkr/move_bk_pipelines
Remove buildkite pipeline
2020-02-21 17:34:09 +00:00
David Baker 6c30af245c Remove buildkite pipeline
Now moved to the pipelines repo
2020-02-21 17:21:42 +00:00
Bruno Windels c9c40a6dde Merge pull request #1226 from matrix-org/bwindels/dontfailonhistoricalcancelafterstart
don't assume verify has been called when receiving a cancellation in verifier
2020-02-21 17:19:03 +00:00
Travis Ralston e748ac3d00 Merge pull request #1221 from matrix-org/travis/qr-binary
Reduce secret size for new binary packing
2020-02-21 10:05:03 -07:00
Bruno Windels aec79f3a79 don't assume verify has been called when receiving a cancellation in verifier 2020-02-21 17:26:29 +01:00
Hubert Chathi bf92cb1522 try to re-fetch devices before giving up on trying to heal a broken olm (#1224) 2020-02-21 10:20:46 -05:00
Bruno Windels 14e1920ff5 fix docs parser error 2020-02-21 13:43:08 +01:00
Bruno Windels c95cdf5a11 add room method for alt_aliases 2020-02-21 13:37:14 +01:00
Bruno Windels c14d0616ea always return null if there is no canonical alias 2020-02-21 13:36:52 +01:00
Hubert Chathi 0112701145 Merge pull request #1223 from matrix-org/uhoreg/misc_rageshake_fixes
misc rageshake fixes
2020-02-20 16:28:49 -05:00
Hubert Chathi cb69515be9 add some logging when sender could not establish an olm session 2020-02-20 14:49:32 -05:00
Hubert Chathi 3cd791e08f add function for getting the user's curve25519 key 2020-02-20 14:44:28 -05:00
Hubert Chathi 6e233e860e remove leftover debugging messages 2020-02-20 14:43:59 -05:00
Hubert Chathi b4f0ea441b remove obsolete comment 2020-02-20 14:43:24 -05:00
Bruno Windels 39974d3a61 Merge pull request #1220 from matrix-org/bwindels/fixhistoricalcancelledrequests
Fix cancelled historical requests not appearing as cancelled
2020-02-20 17:07:35 +00:00
Bruno Windels a998006842 Merge pull request #1217 from matrix-org/bwindels/fixqrcode
Fix renaming error that broke QR code verification
2020-02-20 11:00:39 +00:00
Bruno Windels c4e449fc45 add null check for when there is no response 2020-02-20 11:42:33 +01:00
Travis Ralston 765fbe2182 Reduce secret size for new binary packing
See https://github.com/matrix-org/matrix-react-sdk/pull/4091
2020-02-19 17:21:56 -07:00
Bruno Windels 08dfa73b57 pending excludes observeOnly now, still allow observeOnly requests to get cancelled 2020-02-19 17:51:53 +01:00
RiotRobot a58e7a34e7 v5.0.1 2020-02-19 15:03:04 +00:00
RiotRobot 7a481beec6 Prepare changelog for v5.0.1 2020-02-19 15:03:03 +00:00
Bruno Windels d51fad2de4 Merge pull request #1219 from matrix-org/bwindels/fixaliases
add method for new /aliases endpoint
2020-02-19 10:02:32 +00:00
Bruno Windels c66755a756 jsdoc 2020-02-19 10:13:32 +01:00
Bruno Windels 886ad03505 add method to check server feature flag 2020-02-19 10:08:05 +01:00
Bruno Windels ba33ef0a68 use unstable prefix 2020-02-19 10:07:52 +01:00
Bruno Windels fe97dc3ece add method for new /aliases endpoint 2020-02-18 15:33:41 +01:00
Bruno Windels 76c4875088 fix targetDevice renaming 2020-02-18 11:23:04 +01:00
Bruno Windels 04a3aaee35 Merge pull request #1213 from matrix-org/bwindels/filterverifmethods
method for checking if other party supports verification method
2020-02-18 10:15:49 +00:00
Bruno Windels fef03cda9b Update src/crypto/verification/request/VerificationRequest.js
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2020-02-18 10:03:02 +00:00
Bruno Windels 3292fde41b Merge pull request #1210 from matrix-org/bwindels/localecho2
add local echo state for accepting or declining a verif req
2020-02-18 09:55:09 +00:00
RiotRobot 38cf25ac5a Merge branch 'master' into develop 2020-02-17 11:58:01 +00:00
RiotRobot 13d5d2f958 v5.0.0 2020-02-17 11:55:26 +00:00
RiotRobot 7f6b66c824 Prepare changelog for v5.0.0 2020-02-17 11:55:25 +00:00
Bruno Windels 62c344b633 Merge pull request #1214 from matrix-org/bwindels/workswithrageshakes
make logging compatible with rageshakes
2020-02-14 16:39:05 +00:00
Bruno Windels 75ce2729f9 comment typo 2020-02-14 17:35:07 +01:00
Bruno Windels 6669554867 make logging compatible with rageshakes 2020-02-14 17:31:40 +01:00
Bruno Windels d3294da37c Merge pull request #1209 from matrix-org/bwindels/oneverifrequest
Find existing requests when starting a new verification request
2020-02-14 15:33:06 +00:00
Bruno Windels 9b56bf25cf Update src/crypto/verification/request/InRoomChannel.js
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2020-02-14 14:43:50 +00:00
Bruno Windels e1a33d8a7b Update src/crypto/verification/request/ToDeviceChannel.js
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2020-02-14 13:41:38 +00:00
Bruno Windels 47a1224c13 Merge pull request #1211 from matrix-org/bwindels/logsasmac
log MAC calculation during SAS
2020-02-14 12:54:40 +00:00
Bruno Windels 5c57d81e94 method for checking if other party supports verification method 2020-02-14 13:47:24 +01:00
Bruno Windels edefd3ec88 log MAC calculation 2020-02-14 12:20:02 +01:00
Bruno Windels f15098efde add local echo state for accepting or declining a verif req 2020-02-13 17:27:18 +01:00
RiotRobot 8ee99a0616 v5.0.0-rc.1 2020-02-13 15:41:46 +00:00
RiotRobot 3ace1d04cd Prepare changelog for v5.0.0-rc.1 2020-02-13 15:41:45 +00:00
Bruno Windels 365bb772bc also find existing request for to-device verification 2020-02-13 15:37:21 +01:00
Bruno Windels 5ee6ada973 use pending instead of individual checks 2020-02-13 15:37:04 +01:00
Bruno Windels ee0fa0e687 fix lint 2020-02-13 14:47:35 +01:00
Bruno Windels 0d41f6aafc remove commented out logging 2020-02-13 14:36:18 +01:00
Bruno Windels 91b6499815 more consistent naming 2020-02-13 14:36:09 +01:00
Bruno Windels 7cd1166a47 allow finding existing verif req without starting a new one 2020-02-13 14:31:33 +01:00
Bruno Windels f76cb677ff store sasEvent on verifier so we can get it if we missed show_sas event 2020-02-13 14:31:03 +01:00
Bruno Windels 05e7f4e6f7 look for existing verification request when trying to start a new one 2020-02-13 14:30:38 +01:00
Bruno Windels 6684574bdf Merge pull request #1206 from matrix-org/bwindels/dontpassmethodstoverify
Remove methods argument to verification
2020-02-13 08:51:27 +00:00
Hubert Chathi 36a945f8e2 Merge pull request #1207 from matrix-org/uhoreg/fix_opts_request
don't do a dynamic import of request
2020-02-11 13:54:12 -05:00
Hubert Chathi 6a3d322033 don't do a dynamic import of request 2020-02-11 13:02:34 -05:00
Bruno Windels 00c003ec65 remove methods arg to requestVerification(DM)
as it's easy to have this argument be out of sync from all
the places this is called from the js-sdk. There is also little point,
as you can already specify the methods a consumer of the js-sdk
wants to provide through the verificationMethods option when creating
the client object.
2020-02-11 17:42:49 +01:00
Bruno Windels f4d335c161 use default methods if none are provided to the client 2020-02-11 17:42:17 +01:00
Bruno Windels 659f42139b Merge pull request #1201 from matrix-org/travis/wip/qr
QR self-verification fixes
2020-02-11 15:17:02 +00:00
Bruno Windels 0e791ed022 Merge pull request #1204 from matrix-org/bwindels/logverif
Log every verification event
2020-02-11 13:17:23 +00:00
Bruno Windels 48655aa1a3 log every verification event 2020-02-11 10:08:17 +01:00
Bruno Windels 83fa80cfda Merge pull request #1203 from matrix-org/bwindels/dontrequiredoneconfirmation
dont require .done event from other party
2020-02-11 08:18:46 +00:00
Bruno Windels cf5b5ee085 dont require .done event from other party 2020-02-10 18:00:24 +01:00
Bruno Windels 429a4e3526 fix lint 2020-02-10 17:21:22 +01:00
Zoe d66d4c1cd9 Merge pull request #1202 from matrix-org/foldleft/12221-reset-cross-signing
New option to fully reset Secret Storage keys in boostrapSecretStorage
2020-02-10 09:59:28 +00:00
Zoe 7a1bbdf2dd oops 2020-02-07 15:51:27 +00:00
Travis Ralston 29c1459568 Merge pull request #1190 from matrix-org/travis/qr-code-request-based
Add function to estimate target device for a VerificationRequest
2020-02-07 15:37:49 +00:00
Travis Ralston efad46a8a4 Rename target device prop 2020-02-07 15:37:34 +00:00
Zoe a69c621305 New option to fully reset Secret Storage keys in boostrapSecretStorage 2020-02-07 14:45:10 +00:00
Bruno Windels ad6dde6f26 Merge pull request #1200 from matrix-org/bwindels/4sunlockpurpose
pass ssss item name to callback so we can differentiate UI on it
2020-02-07 08:58:43 +00:00
Bruno Windels 2627e46723 add jsdoc for new param 2020-02-06 18:43:46 +01:00
Bruno Windels 408d70b55e pass ssss item name to callback so we can differentiate UI on it 2020-02-06 16:54:12 +01:00
Hubert Chathi 3f369e528b Merge pull request #1167 from cedricvanrompay/1-olm-device-export-import
add export/import of Olm devices
2020-02-05 20:09:01 -05:00
Zoe 312976294b Merge pull request #1199 from matrix-org/foldleft/types-for-utils
Convert utils.js -> utils.ts
2020-02-05 12:41:17 +00:00
Zoe 77f42c479b Update src/utils.ts
Co-Authored-By: Travis Ralston <travpc@gmail.com>
2020-02-05 11:50:39 +00:00
Zoe d60bd22674 actually let's not get into the business of writing types for our deps 2020-02-05 11:43:11 +00:00
Zoe 2e67f77d3e compiler flags 2020-02-05 11:17:55 +00:00
Zoe 6d8e8e6bd7 fix tests 2020-02-05 11:07:55 +00:00
Zoe 9c01945a05 copyright notice *sigh* 2020-02-05 10:23:24 +00:00
Zoe 7ce5ddd380 lint 2020-02-05 10:14:26 +00:00
Zoe 2b5de914f5 review feedback 2020-02-05 09:57:46 +00:00
Zoe 18a2426707 Convert utils.js -> utils.ts 2020-02-04 19:09:48 +00:00
David Baker 367fac6d54 Merge pull request #1197 from matrix-org/dbkr/stop_signing_yourself
Don't sign ourselves as a user
2020-02-04 14:31:08 +00:00
David Baker 157cc9e5eb Merge remote-tracking branch 'origin/develop' into dbkr/stop_signing_yourself 2020-02-04 14:26:58 +00:00
David Baker 81daf12598 Merge pull request #1196 from matrix-org/dbkr/verfication_logging
Add a bunch of logging to verification
2020-02-04 14:21:08 +00:00
Bruno Windels 9249b0652f Merge pull request #1198 from matrix-org/bwindels/fixverifroomeventtype
Fix: always return a valid string from InRoomChannel.getEventType
2020-02-04 14:16:14 +00:00
Bruno Windels ee4c6b6265 Merge pull request #1195 from matrix-org/bwindels/logoncancel
add logging when a request is being cancelled
2020-02-04 13:08:13 +00:00
David Baker 68deab4a68 We still need to mark our master key locally verified 2020-02-04 12:27:53 +00:00
Bruno Windels c9c765b5b8 fix getEventType 2020-02-04 13:12:38 +01:00
David Baker 616f73d8c6 forgive me, o great linter 2020-02-04 12:12:02 +00:00
Bruno Windels 208c371afb add failing test for getEventType 2020-02-04 13:10:06 +01:00
David Baker 3a59cfa9c0 Don't sign ourselves as a user 2020-02-04 12:09:42 +00:00
David Baker cf94527bd5 Add a bunch of logging to verification
So we have a better idea of what's going on
2020-02-04 12:04:50 +00:00
Travis Ralston fa93479863 Merge pull request #1194 from matrix-org/travis/fix-type
Don't explode verification validation if we don't have an event type
2020-02-04 11:48:25 +00:00
Bruno Windels 8bc0ef8c27 add logging when a request is being cancelled
so we can more easily see (especially for to_device requests)
why something was cancelled
2020-02-04 12:48:02 +01:00
Travis Ralston bd403b6d87 Don't explode verification validation if we don't have an event type
I don't know why this is undefined at this point, or why membership events are ending up here, but this fixes develop for people.

See https://github.com/vector-im/riot-web/issues/12231
2020-02-04 11:46:31 +00:00
Bruno Windels 57a7328065 Merge pull request #1193 from matrix-org/bwindels/dontshowverifrequestnotforme
Fix: verification request appearing for users that are not the receiver or sender if they are in room
2020-02-04 09:39:45 +00:00
Bruno Windels 4945463beb fix lint 2020-02-03 20:12:21 +01:00
Bruno Windels dfafa791f2 fix getOtherPartyUserId 2020-02-03 19:17:40 +01:00
Bruno Windels 5f2cb6b3a4 only an m.room.message with msgtype can be a .request 2020-02-03 19:17:18 +01:00
Bruno Windels 5398fac348 add (failing) tests for getEventType and getOtherPartyUserId 2020-02-03 19:16:48 +01:00
Cédric Van Rompay b217f6aa81 minor doc update (with sign-off)
Signed-off-by: Cédric Van Rompay <cedric.vanrompay@gmail.com>
2020-02-03 10:32:32 +01:00
Cédric Van Rompay ec597bea93 fix new way of calling OlmDevice.init 2020-02-03 10:27:10 +01:00
Cédric Van Rompay 7a5c54fef7 set pickle key through OlmDevice.init 2020-02-03 09:58:18 +01:00
David Baker 4064f18de2 Merge pull request #1192 from matrix-org/dbkr/fix_passthrough_key_get
Fix getting secrets encoded with passthrough keys
2020-02-02 19:12:39 +00:00
David Baker 6d13457172 Fix getting secrets encoded with passthrough keys 2020-02-01 17:29:08 +00:00
Travis Ralston f39518ef93 Unreviewed crypto verification for self 2020-02-01 10:49:32 +00:00
Bruno Windels 4b1cecd246 also set the deviceId on .ready so we know who to send .start to 2020-01-31 14:50:48 +01:00
Cédric Van Rompay 352509fd3a Update src/crypto/OlmDevice.js
Co-Authored-By: Hubert Chathi <hubert@uhoreg.ca>
2020-01-31 11:53:20 +01:00
Cédric Van Rompay d0f08f8839 Update src/crypto/OlmDevice.js
Co-Authored-By: Hubert Chathi <hubert@uhoreg.ca>
2020-01-31 11:53:09 +01:00
Cédric Van Rompay efd38a3471 Update src/crypto/OlmDevice.js
Co-Authored-By: Hubert Chathi <hubert@uhoreg.ca>
2020-01-31 11:52:54 +01:00
Cédric Van Rompay a4e74fea94 fix linting errors 2020-01-31 11:51:17 +01:00
Travis Ralston fdb33b6189 Merge remote-tracking branch 'origin/bwindels/todevicereadystartdone' into travis/wip 2020-01-30 18:06:31 +00:00
Bruno Windels dcbb67838b for the right panel to work, the verifier should send .done events 2020-01-30 18:46:22 +01:00
Bruno Windels 1727d636a3 don't assume both parties have a different userId in verif ping-pong 2020-01-30 18:45:54 +01:00
Travis Ralston 9eadc7f868 Add function to estimate target device for a VerificationRequest
For https://github.com/matrix-org/matrix-react-sdk/pull/4001
2020-01-30 16:57:01 +00:00
Travis Ralston 620118af5f Merge pull request #1175 from matrix-org/travis/update-qr-code
Update QR code handling for new spec
2020-01-30 11:25:57 +00:00
Travis Ralston 3645764f9a Appease the linter 2020-01-30 11:15:25 +00:00
Travis Ralston 769bfeb10f Verify all the things 2020-01-30 11:10:25 +00:00
Travis Ralston 5fbaa9cfa7 Fix verification of the master key 2020-01-29 18:06:25 +00:00
Travis Ralston 007508ba12 Merge branch 'develop' into travis/update-qr-code 2020-01-29 16:57:57 +00:00
David Baker 0f1f18b232 Merge pull request #1188 from matrix-org/dbkr/dont_add_epemeral_events_to_timeline_when_peeking
Don't add ephemeral events to timeline when peeking
2020-01-29 15:17:27 +00:00
David Baker d6b754b133 Merge pull request #1189 from matrix-org/dbkr/be_prepaed
Fix typo
2020-01-29 15:16:56 +00:00
Travis Ralston 1b80c83676 Merge branch 'develop' into travis/update-qr-code 2020-01-29 15:11:06 +00:00
Travis Ralston ec4dc582b6 Remove tests for old QR code stuff 2020-01-29 15:10:35 +00:00
David Baker 65646ff9e2 Fix typo
This would probably just cause apps to wait until the first live
sync had finished rather than the one from the store, so slowing
them down / breaking offline support.
2020-01-29 15:06:19 +00:00
Travis Ralston 92f6ec918b Appease the linter 2020-01-29 15:06:13 +00:00
David Baker 62bd41d2e6 Don't add ephemeral events to timeline when peeking
As hopefully explained by comment.

Fixes https://github.com/vector-im/riot-web/issues/11120
2020-01-29 15:04:09 +00:00
Bruno Windels 9d864ffd60 Merge pull request #1187 from matrix-org/bwindels/fixstartrace-rebased
Verification: resolve race between .start events from both parties
2020-01-29 15:04:00 +00:00
Travis Ralston c45b38cece Actually do the verification 2020-01-29 14:56:28 +00:00
Travis Ralston 0d7aee2c36 Misc cleanup 2020-01-29 14:52:04 +00:00
Travis Ralston be345a523f Fix verification flow 2020-01-29 14:43:37 +00:00
Bruno Windels 470bdf8741 fix tests 2020-01-29 15:19:18 +01:00
Bruno Windels 59319fb55b use logger instead of console 2020-01-29 15:19:07 +01:00
Bruno Windels fb7695fdbc fix unrelated issue: errorFactory returns function, so call it 2020-01-29 15:18:48 +01:00
Bruno Windels 25b7552683 startEvent can always be passed to verifier
as we'll check the sender there to see on which side we are
2020-01-29 15:18:18 +01:00
Bruno Windels 21d520378f apply same algo to pick .start event initially when changing phase
smallest sender userid wins
2020-01-29 15:17:36 +01:00
Bruno Windels 9cd6607520 attempt to switch start event if we already have a verifier 2020-01-29 15:16:54 +01:00
Bruno Windels efd3550f53 support switching startEvent while waiting for .accept on initiator side
if we get a .start event from the other party and we've also sent one,
the .start event with the sender that is first in sorting order should
be taken, and the other one ignored.

At the point where we will receive it, the verifier has already
been returned from beginKeyVerification, so we'll need to switch
start event internally, and retry the verification, now on the
receiver (sending .accept) side instead of initiator side
(sending .start).
2020-01-29 15:13:59 +01:00
Travis Ralston 76402ec8d7 Lie to the verification handling 2020-01-29 13:45:02 +00:00
Travis Ralston f689142806 Define NAME as a property higher up 2020-01-29 10:52:26 +00:00
Travis Ralston fd563bda6a Remove irrelevant verification flows for QR codes
You can't actually get at these through our verification framework - they scan/show steps are pre-verification framework.
2020-01-29 09:26:29 +00:00
Travis Ralston 09a8f7122c Merge branch 'develop' into travis/update-qr-code 2020-01-29 00:18:15 +00:00
David Baker 608fb00844 Merge pull request #1184 from matrix-org/dbkr/new_keybackup_in_bootstrap
Add option to bootstrap to start new key backup
2020-01-28 22:02:10 +00:00
David Baker 5c45e9c306 Add option to bootstrap to start new key backup
The key backup needs to be signed by the cross-signing key so
doing it here allows us to do it before we blow the private part
out of memory.
2020-01-28 19:36:00 +00:00
Travis Ralston 950221dc13 Merge branch 'develop' into travis/update-qr-code 2020-01-28 17:27:38 +00:00
Travis Ralston f816679596 Merge pull request #1182 from matrix-org/travis/null-guards
Add a bunch of null guards to feature checks
2020-01-28 14:24:53 +00:00
Travis Ralston 80ccf18b16 Merge pull request #1183 from salzig/docs/fix_matrix_client_reference
docs: fix MatrixClient reference
2020-01-28 14:13:59 +00:00
Ben Rexin c7abd9062a docs: fix MatrixClient reference 2020-01-28 15:05:13 +01:00
Travis Ralston 4287f2229b Add a bunch of null guards to feature checks 2020-01-28 13:21:01 +00:00
Michael Telatynski 8408055137 Merge pull request #1180 from matrix-org/t3chguy/cs_verification_decoration
Add helper to obtain the cancellation code for a verification request
2020-01-28 11:23:24 +00:00
Michael Telatynski cc0965d703 s/^t/T/ 2020-01-28 11:19:05 +00:00
Michael Telatynski 94b3d9d3e1 Add helper to obtain the cancellation code for a verification request 2020-01-28 11:15:07 +00:00
J. Ryan Stinnett 772bf7d6ff Merge pull request #1178 from matrix-org/jryans/tag-prerelease-next
Publish pre-releases as a separate tag on npm
2020-01-27 22:32:56 +00:00
J. Ryan Stinnett 15c2e4bb07 Publish pre-releases as a separate tag on npm
npm will install the newest version a package has published to the `latest` tag,
including pre-releases, which is not ideal since those may not be ready for
production use yet.

This uses an alternate tag (`next` is a common convention, but it can be
anything) for pre-releases so the default installs only get stable versions.

Fixes https://github.com/vector-im/riot-web/issues/12029
2020-01-27 20:40:35 +00:00
Travis Ralston 419693023f Add untested reciprocate function 2020-01-27 11:41:52 -07:00
Travis Ralston 2d081f2c19 Merge branch 'develop' into travis/update-qr-code 2020-01-27 11:41:05 -07:00
David Baker c76ce1fd85 Merge pull request #1177 from matrix-org/dbkr/fix_passthrough_keys
Fix support for passthrough keys
2020-01-27 16:55:27 +00:00
David Baker f38b4d37e6 Check for the whole thing being null 2020-01-27 16:25:08 +00:00
David Baker 73c92dfc57 Merge pull request #1174 from matrix-org/dbkr/trust_cross_signing_on_verify
Trust our own cross-signing keys if we verify them with another device
2020-01-27 16:11:08 +00:00
David Baker 61c5430deb Fix support for passthrough keys
and add code to fix up ones mis-stored by the old code
2020-01-27 15:50:01 +00:00
J. Ryan Stinnett 21e4c597d9 Merge pull request #1176 from matrix-org/jryans/await-device-list
Ensure cross-signing keys are downloaded when checking trust
2020-01-27 15:34:19 +00:00
J. Ryan Stinnett 4dbeee8cb3 Ignore downloading for tests 2020-01-27 15:28:36 +00:00
J. Ryan Stinnett adc76c636e Merge pull request #1172 from matrix-org/bwindels/reduceveriflogging
Don't log verification validation errors for normal messages
2020-01-27 15:04:00 +00:00
J. Ryan Stinnett 0dbf89b2b4 Ensure cross-signing keys are downloaded when checking trust
When checking cross-signing trust during login, we may not have downloaded keys
yet. This ensures we make an attempt first if needed.

Fixes https://github.com/vector-im/riot-web/issues/12068
2020-01-27 14:55:20 +00:00
Travis Ralston 83241ac17d Update QR code handling for new URL
This doesn't have any meaningful change on the process, just makes it more in line with what we do.
2020-01-27 06:59:04 -07:00
Cédric Van Rompay 6aa5d39357 move new example to own directory 2020-01-27 14:28:50 +01:00
Cédric Van Rompay 1304ecbe03 factor out _initializeFromExportedDevice 2020-01-27 14:12:43 +01:00
RiotRobot aafc027812 Merge branch 'master' into develop 2020-01-27 11:31:09 +00:00
RiotRobot d84e0b166b v4.0.0 2020-01-27 11:28:17 +00:00
RiotRobot d1d46009cd Prepare changelog for v4.0.0 2020-01-27 11:28:17 +00:00
Cédric Van Rompay 3a4b6f0ea0 rename "kwargs" to "opts" 2020-01-27 11:48:28 +01:00
Cédric Van Rompay b3d10ace21 mention export method in import 2020-01-27 11:45:17 +01:00
Cédric Van Rompay c17df7a6f7 fix typo in comments 2020-01-27 11:42:15 +01:00
David Baker 1c13f5026e Merge pull request #1173 from matrix-org/dbkr/fix_bootstrap_cleanup
Fix bootstrap cleanup
2020-01-27 10:18:37 +00:00
David Baker b9cfede888 Trust our own cross-signing keys if we verify them with another device 2020-01-25 20:38:11 +00:00
David Baker 49fd9e90a0 this can be const now 2020-01-25 19:48:36 +00:00
David Baker e09038232e Fix bootstrap cleanup
As hopefully explained in the comment. The symptom of this was that
bootstrapping would work just fine the first time you called it
in any run of the app, but then if called a second time (eg. if you
cancelled by dismissing the password prompt) it would create keys and
upload the public parts but not store the private parts in SSSS,
leaving you with cross signing keys you don't have the private parts
of.

Also use object.assign in the save keys callback just in case we
ever reset a subset of the keys (and also because it makes it a
bit simpler to reason about what objects are where).
2020-01-25 19:42:02 +00:00
Travis Ralston 2cfe310e89 Merge pull request #1155 from matrix-org/travis/qr-verif-rp
QR code verification
2020-01-24 08:55:42 -07:00
Bruno Windels 973c7467e8 Merge pull request #1171 from matrix-org/bwindels/fixverifyowndevice
expose deviceId prop on device channel
2020-01-24 11:24:23 +00:00
Bruno Windels 583df7ed7d don't log verification validation errors for normal messages 2020-01-24 12:23:18 +01:00
Bruno Windels 6d05376f04 expose deviceId prop on device channel
used to check if a verification came through to_device in the toast
2020-01-24 12:01:20 +01:00
Cédric Van Rompay e1f832bfa7 fix linting errors 2020-01-24 09:20:43 +01:00
Travis Ralston b8092cd00b Make the tests pass 2020-01-23 20:41:52 -07:00
Travis Ralston 3c1dca6cef Generate a shared secret if we don't have one 2020-01-23 20:15:02 -07:00
Travis Ralston c0f7dd6fe9 Fix secret size 2020-01-23 20:06:04 -07:00
Travis Ralston 6af6e99480 Expose the request event more readily for consumers 2020-01-23 20:05:56 -07:00
Travis Ralston c5cbe48668 Remove docs too 2020-01-23 19:29:42 -07:00
Travis Ralston 15707956ef Remove private key accessors for cross-signing 2020-01-23 19:29:42 -07:00
Travis Ralston 4668fc87a1 Add cross-signing accessors and QR code stuff 2020-01-23 19:29:42 -07:00
Jack Works 468fb2cc41 chore: remove custom promise, use es6 standard
Signed-off-by: Jack Works <jackworks@protonmail.com>
2020-01-23 19:23:08 -07:00
Jack Works 7c79e7e836 fix: typos
Signed-off-by: Jack Works <jackworks@protonmail.com>
2020-01-23 19:21:19 -07:00
Travis Ralston 925c6ffc3e Merge pull request #1170 from matrix-org/travis/fix-build-release
Move & upgrade babel runtime into dependencies (like it wants)
2020-01-23 15:46:30 -07:00
Travis Ralston 0bf1f48623 Merge pull request #1169 from matrix-org/travis/fix-build
Move & upgrade babel runtime into dependencies (like it wants)
2020-01-23 15:45:53 -07:00
Travis Ralston ffcb1c2513 Move & upgrade babel runtime into dependencies (like it wants)
https://babeljs.io/docs/en/babel-runtime
2020-01-23 15:44:59 -07:00
Travis Ralston f286eb4d11 Move & upgrade babel runtime into dependencies (like it wants)
https://babeljs.io/docs/en/babel-runtime
2020-01-23 15:44:36 -07:00
Cédric Van Rompay 9346c83dc1 fix destructuration of potentially nil value 2020-01-23 18:53:42 +01:00
Bruno Windels a76267f5b0 Merge pull request #1166 from matrix-org/bwindels/verifyowndevicechecks
Add unit tests for verifying your own device, remove .event property on verification request
2020-01-23 17:01:43 +00:00
Cédric Van Rompay 1d3a7b3d52 add example for export/import in browser 2020-01-23 16:55:16 +01:00
Cédric Van Rompay f78f04d553 userId must be included in exported data 2020-01-23 16:55:16 +01:00
Cédric Van Rompay 7b6dabbe9c add high-level export/import methods
not sure how to test these high-level methods though
2020-01-23 16:55:16 +01:00
Cédric Van Rompay ed01b3b8cf stop checking structure of exported data
it should suffice that the exported data
allows to recreate a device that can do crypto
2020-01-23 16:55:16 +01:00
Cédric Van Rompay 7880a30e57 add importing in OlmDevice.init() 2020-01-23 16:55:16 +01:00
Cédric Van Rompay 3a3ff93450 improve export doc 2020-01-23 16:55:16 +01:00
Cédric Van Rompay 3a1cdd37a3 move export test with other Olm tests to have active sessions 2020-01-23 16:55:16 +01:00
Cédric Van Rompay 8db38f8e75 fix output of getAllEndToEndSessions 2020-01-23 16:55:16 +01:00
Cédric Van Rompay ff24ef4ee5 add OlmDevice.prototype.export
- only exporting account and P2P sessions
- test is halfway done:
  - it only prints the export result instead of running assertions on it
  - there are no sessions to export

Note: to run only the added test:

    node_modules/.bin/jest spec/unit/crypto/algorithms/olm.spec.js --testEnvironment node --testNamePattern OlmDevice
2020-01-23 16:55:16 +01:00
Bruno Windels 3faeec4add fix lint 2020-01-23 15:59:47 +01:00
Bruno Windels 7d56ee5084 with the change in the linked react-sdk PR, event isn't used anymore 2020-01-23 15:52:23 +01:00
Bruno Windels b2afaabb8c add unit tests for verifying your own device over to_device messages 2020-01-23 15:52:07 +01:00
Bruno Windels 3efaf90bc8 Merge pull request #1163 from matrix-org/bwindels/verificationaccceptedbyotherdevice
For dm-verification, also consider events sent by other devices of same user as "our" events
2020-01-23 13:27:15 +00:00
Bruno Windels 0c52887688 copyright year
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2020-01-23 10:51:19 +00:00
David Baker 8aa1c1545e Merge pull request #1164 from matrix-org/dbkr/prepublish_rel
Add a prepare script
2020-01-22 20:59:40 +00:00
David Baker 7c84f421c5 Turns out prepublish is deprecated and should be prepare 2020-01-22 20:58:25 +00:00
David Baker 42a1dea7ad Add a prepublish script
So we actually build the lib directory before publishing it
2020-01-22 20:58:18 +00:00
David Baker d5e9155a33 Merge pull request #1161 from matrix-org/dbkr/prepublish
Add a prepare script
2020-01-22 20:56:59 +00:00
Michael Telatynski 5def5ab074 Merge pull request #1162 from matrix-org/t3chguy/crypto/keys/upload/deviceId
Remove :deviceId from /keys/upload/:deviceId as not spec-compliant
2020-01-22 17:13:21 +00:00
Bruno Windels 1b242e636b remove obsolete comment 2020-01-22 17:39:21 +01:00
Bruno Windels 05f05c889a don't verify in observeOnly mode 2020-01-22 17:39:21 +01:00
Bruno Windels 1367e285c8 have channel decide what is considered "sent by us"
for in room verification, if another client accepts the request,
we still want to observe so those events should still be
considered ours, so looking at from_device doesn't work there.
2020-01-22 17:39:21 +01:00
Bruno Windels 45ec3e0bb9 also emit if the phase didn't change but observeOnly did 2020-01-22 17:39:21 +01:00
Bruno Windels dc38f78da2 add unit tests for verification request 2020-01-22 17:39:21 +01:00
Michael Telatynski 1b6a74fd93 Remove :deviceId from /keys/upload/:deviceId as not spec-compliant 2020-01-22 15:20:13 +00:00
David Baker 9d8a1494aa Turns out prepublish is deprecated and should be prepare 2020-01-22 14:14:24 +00:00
David Baker 08465cf236 Add a prepublish script
So we actually build the lib directory before publishing it
2020-01-22 14:09:01 +00:00
Damir Jelić 7016848401 Merge branch 'poljar/timeline-window-refactor' into develop 2020-01-21 17:01:16 +01:00
poljar bdd2a9e7e8 timeline-window: Small docfix.
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2020-01-21 16:55:12 +01:00
David Baker 80256e6782 Merge pull request #1158 from matrix-org/dbkr/upload_device_keys_empty_auth
Allow a device key upload request without auth
2020-01-21 15:03:58 +00:00
Damir Jelić 7907ef44f8 timeline-window: Refactor out and expose the logic to extend the window. 2020-01-21 15:42:55 +01:00
Damir Jelić 3a97a24686 timeline-window: Refactor out the TimelineIndex getting logic. 2020-01-21 15:21:10 +01:00
David Baker 7f208ed44e Allow a device key upload request without auth
This is useful for querying the supported auth methods.
2020-01-21 11:35:49 +00:00
Bruno Windels 22e6cfaebb Merge pull request #1140 from matrix-org/bwindels/verification-right-panel
Support for .ready verification event (MSC2366) & other things
2020-01-20 17:17:51 +00:00
Bruno Windels 9d6f873048 remove obsolete and now broken method
a request should be accepted by calling accept() on the request.
2020-01-20 18:13:18 +01:00
Bruno Windels d526229a0f update jsdoc of requestVerificationDM
which now returns a Promise of VerificationRequest instead of verifier
2020-01-20 18:12:52 +01:00
Bruno Windels aac68290ac remove obsolete comment 2020-01-20 17:56:28 +01:00
Bruno Windels bd9a2c13eb implement API change in sas test for requestVerificationDM 2020-01-20 17:55:48 +01:00
Bruno Windels e5c65d53f8 set transaction_id for remote echos in TestClient
as InRoomChannel looks at this to decide whether an event is
a remote echo (and to pass it to the verifier or not)
2020-01-20 17:54:26 +01:00
Bruno Windels 121e9d0225 don't overwrite a request when the remote echo arrives before event_id 2020-01-20 17:39:18 +01:00
Bruno Windels c12a3b6610 more fixup: make sure remote echo doesn't arrive earlier for TestClient 2020-01-20 17:35:44 +01:00
RiotRobot 43fee73924 v4.0.0-rc.1 2020-01-20 14:24:36 +00:00
RiotRobot b72e9cb36c Prepare changelog for v4.0.0-rc.1 2020-01-20 14:24:35 +00:00
Bruno Windels 77d0a76186 fixup: another timeout 2020-01-20 14:52:34 +01:00
Bruno Windels e89528315d enable fake timers for consistency
although it doesn't make or break the test
2020-01-20 14:04:32 +01:00
Bruno Windels c34ccc9d53 adjust test: requestVerification returns the request instead of verifier 2020-01-20 14:03:43 +01:00
Bruno Windels e51ba795f3 to make this work while using fake timers, don't use setTimeout
but instead use Promise.resolved() as then always runs in the next tick.
2020-01-20 13:56:39 +01:00
J. Ryan Stinnett 737dcc1d29 Merge pull request #1154 from matrix-org/jryans/complete-sec-confused
Convert secret storage to new account data API
2020-01-20 11:30:55 +00:00
Travis Ralston dba08d230e Merge pull request #1157 from aaronraimist/v5-safe
Add v5 as a safe room version
2020-01-18 19:29:44 -07:00
Aaron Raimist 15fb363874 Add v5 as a safe room version
Signed-off-by: Aaron Raimist <aaron@raim.ist>
2020-01-18 16:37:19 -06:00
Bruno Windels cbe2965849 mention reason in cancellation error 2020-01-17 19:01:30 +01:00
Bruno Windels 59bfc45856 use setTimeout of setInterval 2020-01-17 19:01:08 +01:00
J. Ryan Stinnett ceb4581f91 Convert secret storage to new account data API
This converts all secret storage to use a newer account data API which uses
cached data in stored when available, but also knows how to ask the homeserver
in case it's invoked during early client startup before the initial sync.

As a consequence, it means most secret storage APIs are now async.

Part of https://github.com/vector-im/riot-web/issues/11901
2020-01-17 17:56:05 +00:00
Bruno Windels 07cc93cca2 fix lint 2020-01-17 16:58:19 +01:00
Travis Ralston 1205178e26 Merge branch 'develop' into bwindels/verification-right-panel 2020-01-16 13:13:00 -07:00
J. Ryan Stinnett 8217c0f05f Merge pull request #1153 from matrix-org/jryans/cross-signing-setup
Add API to get account data from server
2020-01-16 16:42:12 +00:00
J. Ryan Stinnett c5c27b3cb0 Add API to get account data from server
This adds an API account data getter that bypasses the local store and goes
directly to the homeserver.

Part of https://github.com/vector-im/riot-web/issues/11214
2020-01-15 21:17:53 +00:00
Travis Ralston 04bbfae08e Merge pull request #1151 from matrix-org/travis/sourcemaps
Fix sourcemaps by refactoring the build system
2020-01-15 10:11:25 -07:00
Travis Ralston b3efa73eda Fix conflict in megolm.js 2020-01-15 09:06:30 -07:00
Travis Ralston f3efac059c Merge branch 'develop' into travis/sourcemaps 2020-01-15 09:03:21 -07:00
Hubert Chathi 9fb4ed2ec0 Merge pull request #1146 from uhoreg/reporting_olm_error
record, report, and notify about olm errors
2020-01-15 09:37:43 -05:00
Hubert Chathi f19013143a fix indexedDB storage and retry decryption when we get an olm error 2020-01-14 23:47:05 -05:00
Hubert Chathi ea3ee9bea5 Merge pull request #1148 from matrix-org/erikj/per_user_device_messages
Send device messages for the same user in same API call.
2020-01-14 21:33:46 -05:00
Travis Ralston ccca6f4b6d Re-add dist script usage to js-sdk and release script 2020-01-14 14:44:06 -07:00
David Baker 6a583d2ba6 Merge pull request #1150 from matrix-org/dbkr/dont_error_on_unknown_devices
Add an option to ignore unverified devices
2020-01-14 17:11:47 +00:00
Travis Ralston 4049a32871 Fix imports in crypto index post-merge 2020-01-14 10:08:26 -07:00
Travis Ralston 331c9ce1ff [CONFLICT CHUNKS] Merge branch 'develop' into travis/sourcemaps 2020-01-14 10:04:36 -07:00
David Baker 81ab2aca37 spelling
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2020-01-14 14:10:17 +00:00
David Baker 564b8276bf Merge pull request #1144 from matrix-org/dbkr/key_backup_resign
Sign key backup with cross-signing key on upgrade
2020-01-14 11:30:06 +00:00
Erik Johnston b4a93d2dc3 Also apply cahnge to '_splitBlockedDevices' 2020-01-14 10:09:19 +00:00
Erik Johnston 260040b919 Rename var to match new function 2020-01-14 10:07:54 +00:00
J. Ryan Stinnett 8dbef8b68e Merge pull request #1145 from aaronraimist/lock
Emoji verification: Change name of 🔒 to lock
2020-01-13 21:32:49 +00:00
Travis Ralston 458b2d422d Merge branch 'develop' into travis/sourcemaps 2020-01-13 11:17:27 -07:00
David Baker ee51357dbc Add an option to ignore unverified devices
Hopefully all necessary information is on the docstring.

Default behaviour remains unchanged.
2020-01-13 17:37:38 +00:00
Hubert Chathi fa679e873d Merge pull request #1147 from uhoreg/separate_encrypted_content
use a separate object for each encrypted content
2020-01-13 10:17:32 -05:00
Erik Johnston ed3fded8e8 Send device messages for the same user in same API call.
Currently we split the device messages up to limit the number per call,
but that can end up splitting messages to a given users device over
separate API calls. This is fine, but means that the server can't e.g.
bundle them into a single EDU for remote users or sanity check that the
client is sending to the right set of devices (i.e. its device list
cache isn't wrong).
2020-01-13 13:43:00 +00:00
RiotRobot 92df82bfa9 Merge branch 'master' into develop 2020-01-13 12:55:18 +00:00
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
Hubert Chathi 0a9959bffb use a separate object for each encrypted content
so that we don't duplicate the ciphertext for everyone
2020-01-11 15:49:01 -05:00
Hubert Chathi b3a16cb852 lint (and add a comment) 2020-01-10 13:36:11 -05:00
Aaron Raimist 9beb259333 Emoji verification: Change name of 🔒 to lock
Signed-off-by: Aaron Raimist <aaron@raim.ist>
2020-01-09 21:55:43 -06:00
Hubert Chathi 63c57e8e02 record, report, and notify about olm errors 2020-01-09 22:19:35 -05:00
David Baker 0448a7ea68 Sign key backup with cross-signing key on upgrade
Add a signature from the cross-signing master key to the key
backup when upgrading the key backup into cross-signing.

For https://github.com/vector-im/riot-web/issues/11747
2020-01-09 20:46:36 +00:00
Travis Ralston 5bd005b28a Merge pull request #1143 from matrix-org/travis/sourcemaps-dev
Sourcemaps: develop -> feature branch
2020-01-07 15:30:56 -07:00
Travis Ralston 3aec6367d1 Fix OlmDevice import for "algorithms" to fix tests 2020-01-07 15:09:46 -07:00
Travis Ralston cea3831c20 Fix merge conflicts
This is done outside of the merge to highlight the changes, hopefully.
2020-01-07 14:43:36 -07:00
Travis Ralston 18ccceca2d [CONFLICT CHUNKS] Merge branch 'develop' into travis/sourcemaps-dev 2020-01-07 14:37:17 -07:00
Travis Ralston fffcdcb514 Merge pull request #1134 from matrix-org/travis/babel7-wp-media
Use a safer import/export scheme for the ContentRepo utilities
2020-01-07 14:27:36 -07:00
Hubert Chathi efadf374d6 Merge pull request #1142 from uhoreg/reporting_no_key_fix
Fix error handling in decryptGroupMessage
2020-01-07 11:36:53 -05:00
Hubert Chathi 55ecb40190 don't keep processing if we have an error 2020-01-07 11:25:30 -05:00
Hubert Chathi 01f6b3dfc6 notify devices when we don't send them keys (#1135)
and handle incoming notifications
2020-01-06 17:47:22 -05: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
Travis Ralston f4d1c5c006 Switch back to plain export functions instead of class 2020-01-03 12:16:54 -07:00
Bruno Windels 72fd1e4e7c add note to fix bug later 2020-01-03 18:21:33 +01:00
Bruno Windels f44e0a8e12 parenthesis in wrong place broke logic 2020-01-03 18:21:18 +01:00
Bruno Windels 9338d9c2a6 commit logging 2020-01-03 18:20:59 +01:00
Bruno Windels 75fc25feb5 fix method names 2020-01-03 18:20:50 +01:00
Bruno Windels 5919874f6f check !unsent instead of requested for emitting the crypto.request event 2020-01-03 18:20:16 +01:00
Bruno Windels 213bb9dba2 allow to move straight from UNSENT to STARTED
this was one of the things breaking to_device verification
2020-01-03 18:19:49 +01:00
Bruno Windels 3a9dc37d02 new state machine relies on having remote echos, so fake for to_device 2020-01-03 18:18:39 +01:00
Bruno Windels 423c8a886d use isRemoteEcho to determine if the event is theirs or not
rather than the sender and from_device (which is not always set)

as this was one of the things breaking to_device verification
of ones own devices.
2020-01-03 18:16:25 +01: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
Bruno Windels 3ec8233a2d fixes & implement timeout 2020-01-03 13:42:06 +01:00
Bruno Windels 8ed51c806e don't cancel or timeout when verify isn't called 2020-01-03 13:42:06 +01:00
Bruno Windels 57135a898f don't mark events loaded from cache as live events
this makes the verifier want to interact with the other party
when just reloading the session.
2020-01-03 13:42:06 +01:00
Bruno Windels 0d3d27a519 fixes and cleanup for historical 2020-01-03 13:42:06 +01:00
Bruno Windels cf42ad83da WIP historical 2020-01-03 13:42:06 +01:00
Bruno Windels e7bcb61a3b attempt at only creating verifier for live events
but doesn't work yet? data where liveEvent is fished out is undefined
2020-01-03 13:42:06 +01:00
Bruno Windels 883b83f1da move blocking non-participating users back to InRoomChannel
as it doesn't need to happen for ToDeviceChannel
2020-01-03 13:42:06 +01:00
Bruno Windels 48977e6eaa get other party user id by inspecting initial event sender/to fields
also fail validation with any event not sent by or directed to us
2020-01-03 13:42:06 +01:00
Bruno Windels efe2488155 get other user id from channel
next up is inspecting the .request event to
determine it reliably in InRoomChannel
2020-01-03 13:42:06 +01:00
Bruno Windels 29c04b6f9c only move to PHASE_DONE when both .done events are received
as once in done, the request is removed from the request map
and the second .done event that comes in will not find the request
anymore, so the request wouldn't be attached to the event anymore,
breaking rendering it in the timeline.
2020-01-03 13:42:06 +01:00
Bruno Windels 984b6234d2 don't block remote echos to VerificationRequests
also put logic to block non-participating senders in VerificationRequest
so it is shared between both channels.

Remote echo's should not be passed to the verifier though.
2020-01-03 13:42:06 +01:00
Bruno Windels dac4a5452d make this a public prop 2020-01-03 13:42:06 +01:00
Bruno Windels 5f9e82204a more ready and remote echo support 2020-01-03 13:42:06 +01:00
Bruno Windels c4142d93c3 store in-room verification requests by roomId, txnId
as it's harder to determine the other side of a request, given
the in-room code also processes remote echos for own events.
2020-01-03 13:42:06 +01:00
Bruno Windels b34a2c7ee2 WIP 2020-01-03 13:42:06 +01:00
Bruno Windels cd7cc1b71f set verification request on event 2020-01-03 13:42:06 +01:00
Bruno Windels 4c6dd564a4 filter verification methods from argument 2020-01-03 13:42:06 +01:00
Bruno Windels 28e46a82ea expose common phases as properties
so we don't need to import the PHASE_ constants where we need to check
2020-01-03 13:42:06 +01:00
Bruno Windels 10e294784e waitForVerifier is unused now, make it more broadly useful with callback 2020-01-03 13:42:06 +01:00
Bruno Windels 2da725340c return request instead of verifier from verification methods
as MSC2366 adds an extra interactive step to the verification process,
we can't wait for the verifier after sending the request.

This is a breaking change in the js-sdk as it changes the return type
of an existing method.
2020-01-03 13:42:06 +01:00
Bruno Windels 882d3a765d support .ready event in VerificationRequest 2020-01-03 13:42:06 +01:00
Travis Ralston e52e2f10bf Merge pull request #1131 from matrix-org/travis/babel7-wp-main
Add additional properties to package.json for riot-web's webpack
2020-01-02 10:51:52 -07:00
Travis Ralston dfc19e79f1 Merge pull request #1133 from matrix-org/travis/babel7-wp-idb
Fix import for indexeddb crypto store
2019-12-27 12:04:11 -07:00
Travis Ralston f59bd3da7a Merge pull request #1132 from matrix-org/travis/babel7-wp-request
Use the right request when creating clients
2019-12-27 11:56:52 -07:00
Travis Ralston 50791e3aa7 Make ContentRepo a class for easier importing
Exporting it the way we were was causing problems for webpack way down
the line, so we export it differently here to get around that. We also
have to fix all the import references so we import the right thing.
2019-12-22 20:51:30 -07:00
Travis Ralston 8211b2358f Fix import for indexeddb crypto store 2019-12-22 20:48:24 -07:00
Travis Ralston f2e1f3393d Add additional properties to package.json for riot-web's webpack
See https://github.com/vector-im/riot-web/pull/11679/commits/a1c9551bc8a1a6d61afed7e87ff7cebb3042a5ac
2019-12-22 20:47:45 -07:00
Travis Ralston 0ffec0a32d Use the right request when creating clients 2019-12-22 20:45:04 -07:00
Travis Ralston 1e5e705458 Regen lockfile 2019-12-19 17:10:32 -07:00
Travis Ralston f2af6ea60d Merge pull request #1127 from matrix-org/travis/babel7-btargets
Target NodeJS 10, minified browser bundle, and other publishing/package things
2019-12-19 17:08:57 -07:00
Travis Ralston de9187fee2 Merge branch 'travis/sourcemaps' into travis/babel7-btargets 2019-12-19 17:08:50 -07:00
Travis Ralston 5eed091185 Merge pull request #1126 from matrix-org/travis/babel7-updated-sourcemaps
Re-focus sourcemap generation
2019-12-19 17:08:03 -07:00
Travis Ralston 06644b5748 Merge pull request #1125 from matrix-org/travis/babel7-cleanup
Remove ancient polyfill for prototype inheritance
2019-12-19 17:07:56 -07:00
Travis Ralston bb853f65e0 Merge pull request #1124 from matrix-org/travis/babel7-test-sourcemaps
Remove "source-map-support" from tests because it makes sourcemaps worse
2019-12-19 17:07:49 -07:00
Travis Ralston eb830dd014 Merge pull request #1123 from matrix-org/travis/babel7-strict
Remove ancient "use strict" annotations
2019-12-19 17:07:41 -07:00
Travis Ralston de82d1e90c Merge pull request #1122 from matrix-org/travis/babel7-src-es6
Use ES6 imports/exports instead of older CommonJS ones
2019-12-19 17:07:29 -07:00
Travis Ralston 53e838083c Fix terser for new sourcemap handling 2019-12-19 14:04:01 -07:00
Travis Ralston 975368de8f Externalize our sourcemaps for the browser bundle 2019-12-19 13:58:08 -07:00
Travis Ralston 89173be055 Update README to describe build targets better 2019-12-19 13:42:47 -07: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
Travis Ralston b376a7c399 Improve minification
uglifify was being applied inline, which only resulted in one asset. With this we've gone back to using `terser` to generate the minified version of the browser bundle, which also exports sourcemaps interpretted from browserify. 

The babelify options have changed to ensure that browser-safe code gets published so terser can more effectively parse it. It doesn't like things like classes it seems, but is fine with let/const and such. The preset-env preset automatically knows it is targeting a browser.
2019-12-19 12:25:28 -07:00
Travis Ralston 2df262d877 Reword concerns about default exports 2019-12-19 11:56:47 -07: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
Travis Ralston ffb0e27efa We don't need no dist 2019-12-18 11:37:21 -07:00
Travis Ralston e71c4b3bc4 Publish src to npm as well
So downstream projects can use it via /src if they really want to.
2019-12-17 19:35:00 -07:00
Travis Ralston 85a0adb004 Reorder babel arguments to leave more of it to the config 2019-12-17 19:31:53 -07:00
Travis Ralston f1475cd3d7 Target NodeJS 10 and minified browser bundle
We release with Node 10 currently, so we should use that. The browser bundle is minfied because we want to keep it, so we might as well shave about 1mb off of it.
2019-12-17 16:18:11 -07:00
Travis Ralston 8c14812537 Re-focus sourcemap generation
We'll let babel decide where best to put the sourcemaps. We previously needed inline sourcemaps for browserify to work, though `babelify` takes care of this now that we use `src/` (without sourcemaps in `lib/` being inline, the transform wouldn't work).

Typescript sourcemaps have also been enabled as a mental reminder that they will be exported. Babel is the only thing that uses the tsconfig for generation right now, and it appears to ignore the sourcemaps field.
2019-12-17 16:14:14 -07:00
Travis Ralston 27aedf0563 Remove ancient polyfill for prototype inheritance
None of our targets care about this.
2019-12-17 16:07:56 -07:00
Travis Ralston 95c2c1643e Remove "source-map-support" from tests because it makes sourcemaps worse
Now that we're pointing at `src/` for tests, we can stop trying to load source maps from random places. With this dependency used, source maps are off by a few lines.
2019-12-17 15:45:15 -07:00
Travis Ralston f952f6742f Remove ancient "use strict" annotations
We don't need these anymore. Theoretically this commit could go to develop, but for safety it's going to `travis/sourcemaps` first.
2019-12-17 15:43:02 -07:00
Travis Ralston f3a10a8166 Appease the linter's line length limit 2019-12-17 15:27:17 -07:00
Travis Ralston 0790201cca Add tsify so the browser bundle can use src/ 2019-12-17 15:21:22 -07:00
Travis Ralston 5938c49453 Move index files for outputs and update pipeline
Having them in `src/` helps IDEs do autocomplete a bit more nicely, and helps us not get confused about which one is referencing which. They have also been converted to TypeScript for typings to be generated.
2019-12-17 15:16:37 -07:00
Travis Ralston 14fb080f80 Document ES6 changes 2019-12-17 15:16:37 -07:00
Travis Ralston 034b8db070 Convert tests to ES6
The earlier commit, d3ce0cb82f, has most of the juicy details on this. In addition to d3ce's changes, we also:
* Use `TestClient` in many integration tests due to subtle behaviour changes in imports when switching to ES6. Namely the behaviour where setting the request function is less reliable in the way we did it, but `TestClient` is very reliable.
* We now use the Olm loader more often to avoid having to maintain so much duplicate code. This makes the imports slightly easier to read.
2019-12-17 15:16:37 -07:00
Travis Ralston d3ce0cb82f Convert src to ES6
The bulk of this is just export/import changes, though there's a couple pieces to highlight:
* We no longer use default exports. This is because it's discouraged by the JS community, though not in any official capacity.
* We now use `polyfillSuper` for some prototype inheritance because the tests, and sometimes webpack, break on "cannot call EncryptionAlgorithm without 'new'". It's very much a workaround, and definitely not needed when we use real classes.

There is some import shuffling to help keep the imports clean - this was done by my IDE.
2019-12-17 15:14:22 -07:00
Travis Ralston 4dbda8dffd Merge pull request #1113 from matrix-org/travis/babel7-watcher
[BREAKING] Refactor the entire build process
2019-12-12 16:09:18 -07:00
Travis Ralston 01f32e0f45 Undo change to utils import 2019-12-12 14:14:19 -07: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
Travis Ralston 86c530e967 Leave the description alone 2019-12-12 10:27:02 -07:00
J. Ryan Stinnett 049b769f68 Add docs 2019-12-12 17:27:01 +00:00
Travis Ralston dcd6626fe6 Fix readme for new lack of minification 2019-12-12 10:26:44 -07:00
Travis Ralston 601cefe975 Fix the release script for new build process
It doesn't seem to care what the version is, so just build the SDK normally.
2019-12-12 10:25:19 -07: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
Travis Ralston 1984cf02cf Merge branch 'develop' into travis/babel7-watcher 2019-12-11 17:22:42 -07: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 4bdabbfbe9 [BREAKING] Refactor the entire build process
For https://github.com/vector-im/riot-web/issues/8880

Features:
* Export modern JS
* Export typings
* Export source maps that actually mean something
* No longer supporting minified builds

This is a step towards being a boring SDK and not anticipating an install location. 

This commit requires a major version bump of the SDK.
2019-12-10 13:25:07 -07: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
RiotRobot 358f13500b v2.4.6-rc.1 2019-12-04 11:57:08 +00:00
RiotRobot 016f16954a Prepare changelog for v2.4.6-rc.1 2019-12-04 11:57:07 +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
Travis Ralston c1543545d2 Merge pull request #1102 from matrix-org/travis/aliases
Update alias handling
2019-12-03 13:54:27 -07:00
Travis Ralston 5da936d96a Fix tests 2019-12-03 13:38:40 -07:00
Travis Ralston 0dead73837 Update alias handling
Fixes https://github.com/vector-im/riot-web/issues/11551
2019-12-03 12:29:50 -07: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
Hubert Chathi 92ca2386ea Merge pull request #1096 from uhoreg/fix_sas_unit_test
increase timeout on flush to fix failing unit test
2019-11-28 17:19:41 -05:00
Hubert Chathi 59b25d6837 increase timeout on flush to fix failing unit test
also remove unused requests
2019-11-28 16:53:21 -05:00
Travis Ralston a6f7936311 Merge pull request #1095 from matrix-org/travis/fix-tests-18
Disable broken cross-signing test
2019-11-28 09:11:05 -07: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
Travis Ralston 7392b4de17 xit instead of comment 2019-11-27 19:23:59 -07:00
Travis Ralston 79b0a5fada Add issue to comment 2019-11-27 19:21:57 -07:00
Travis Ralston aee9442e52 Disable broken cross-signing test
I don't know why it's broken, but the two errors I can get out of it are "unknown device for verification" and "user_signing key does not match". Someone who knows a bit more about cross-signing will probably need to take a look at this one.

Fixes https://github.com/vector-im/riot-web/issues/11520 (technically)
Opened https://github.com/vector-im/riot-web/issues/11545 to fix this correctly.
2019-11-27 19:20:09 -07:00
J. Ryan Stinnett d5000820fd Fix comment typo 2019-11-27 17:19:41 +00:00
Travis Ralston 569d5d1fce Merge pull request #1094 from matrix-org/travis/fix-tests-13
Fix a couple SAS tests
2019-11-27 09:24:27 -07: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
RiotRobot 6ea8003df2 Merge branch 'master' into develop 2019-11-27 10:28:14 +00:00
RiotRobot c8ab82010a v2.4.5 2019-11-27 10:17:54 +00:00
RiotRobot bf1bec9c6c Prepare changelog for v2.4.5 2019-11-27 10:17:54 +00:00
Travis Ralston e0c90ec9e3 Fix test flakes in SAS verification with old MAC
This has similar fixes to 7ad5021147

Part of https://github.com/vector-im/riot-web/issues/11520
2019-11-26 23:29:06 -07:00
Travis Ralston 7ad5021147 Fix SAS verification test
There's 3 things going on in this commit:
1. `this` is maintained in the tests. Some binds are added instead of the `.call(this, ...)` syntax.
2. We use the right `origSendToDevice`
3. We ensure `downloadKeys` is actually on the client

The combination of these 3 fixes makes the test pass.

Part of https://github.com/vector-im/riot-web/issues/11520
2019-11-26 23:24:37 -07:00
Matthew Hodgson fd73c3fb3a fix bogus logline 2019-11-27 01:19:17 +00:00
Travis Ralston e3dbf7cc41 Merge pull request #1093 from matrix-org/travis/fix-tests-3
Fix Olm unwedging test
2019-11-26 15:32:50 -07:00
Travis Ralston 18749c580e Fix Olm unwedging test
Deep within the crypto layers we call `getId()`, and when we don't have that function the async call on the emitter fails but doesn't fail the test. This manifests as a timeout because the code path that would call the thing blew up.
2019-11-26 15:06:56 -07:00
J. Ryan Stinnett 396db30fbf Update tests 2019-11-26 12:04:14 -07:00
J. Ryan Stinnett 6b38868de6 Relax identity server discovery checks to FAIL_PROMPT
As discussed in MSC2284, this relaxes the identity server discovery to a
`FAIL_PROMPT` state so that clients can choose to warn and continue.

Part of https://github.com/vector-im/riot-web/issues/11102
Implements https://github.com/matrix-org/matrix-doc/pull/2284
2019-11-26 12:04:07 -07:00
Matthew Hodgson 01a46ad880 log outbound to_device msgs for tracking keyshares 2019-11-26 12:03:34 -07:00
Matthew Hodgson 46f8251e94 s/console/logger/ as per review 2019-11-26 12:03:28 -07:00
Matthew Hodgson 77f882f45a log keyshare ID 2019-11-26 12:03:24 -07:00
Matthew Hodgson 8c72fd104e lint 2019-11-26 12:03:20 -07:00
Matthew Hodgson 549656884b expand e2ee logging to better debug UISIs 2019-11-26 12:03:11 -07:00
Matthew Hodgson 5b8b0a8aa3 log outbound to_device msgs for tracking keyshares 2019-11-26 18:35:25 +00:00
Travis Ralston b1924d4db6 Merge pull request #1089 from matrix-org/travis/upgrade-notifications
Fix empty string handling in push notifications
2019-11-26 11:00:24 -07:00
Travis Ralston 1b877118ef Only do one type check 2019-11-26 10:31:04 -07:00
Travis Ralston 682a5daf1c Merge branch 'develop' into travis/upgrade-notifications 2019-11-26 10:29:40 -07:00
Matthew Hodgson fcbfaac1fd Merge pull request #1090 from matrix-org/matthew/more_e2ee_logging
expand e2ee logging to better debug UISIs
2019-11-26 10:18:28 +00:00
Matthew Hodgson 3787b6f1c7 s/console/logger/ as per review 2019-11-26 09:07:23 +00:00
Matthew Hodgson 6e08835496 log keyshare ID 2019-11-26 01:58:04 +00:00
Matthew Hodgson 191695da5a lint 2019-11-26 01:41:59 +00:00
Matthew Hodgson 2215087f96 expand e2ee logging to better debug UISIs 2019-11-26 01:17:12 +00:00
Michael Telatynski 32234ee7fc Merge pull request #1087 from matrix-org/t3chguy/remove_bluebird_11
Remove Bluebird: phase 2
2019-11-26 00:03:22 +00:00
Travis Ralston aa37f697bf Fix empty string handling in push notifications
Fixes https://github.com/vector-im/riot-web/issues/11460

Empty strings are falsey, and the state key match for a tombstone event is an empty string. Ergo, nothing happens because all the conditions fail.
2019-11-25 16:35:27 -07:00
David Baker 49448fafaa Correct comment (it's not just a cache) 2019-11-25 15:05:10 +00:00
Michael Telatynski 057303d57c s/beforeEach/beforeAll/ for Olm.init() and cleanup sas.spec.js 2019-11-25 13:26:10 +00:00
RiotRobot ccc85d98e2 Merge branch 'master' into develop 2019-11-25 13:24:08 +00:00
RiotRobot c30a8b5a29 v2.4.4 2019-11-25 13:10:04 +00:00
RiotRobot 295010893d Prepare changelog for v2.4.4 2019-11-25 13:10:04 +00:00
Michael Telatynski 7fb807919c Stop using bluebird .returns and .spread 2019-11-25 12:31:46 +00:00
Michael Telatynski bd8f8ef28d Replace yet more deferreds 2019-11-25 11:28:09 +00:00
Michael Telatynski 3901a381cc replace another couple of deferreds 2019-11-25 11:18:32 +00:00
J. Ryan Stinnett 12f6e51ef6 Merge pull request #1062 from matrix-org/jryans/identity-disco-opt
Relax identity server discovery checks to FAIL_PROMPT
2019-11-25 10:48:53 +00:00
Travis Ralston aa8454e30a Merge pull request #1061 from beaclnd92/develop
Fix incorrect return value of MatrixClient.prototype.uploadKeys
2019-11-24 22:19:09 -07:00
Travis Ralston 6b70230e0d Merge branch 'develop' into develop2-test 2019-11-24 22:13:53 -07:00
Michael Telatynski 5e0ba9971c nothing works anymore :(( 2019-11-23 12:18:39 +00:00
David Baker fa577c9475 Merge pull request #1086 from matrix-org/dbkr/fix_calls_in_e2e_rooms
Fix calls in e2e rooms
2019-11-22 17:48:33 +00:00
Bruno Windels 11a958b8ca Merge pull request #1085 from matrix-org/bwindels/verif-toasts
Monitor verification request over DM as well
2019-11-22 16:39:07 +00:00
Bruno Windels 6952db6762 no need to filter here anymore when listening for timeline, also remove obsolete docs 2019-11-22 17:32:37 +01:00
Bruno Windels 51898cffe8 add comments for timeout constants 2019-11-22 17:31:48 +01:00
David Baker d8337d703d Use the right variable name 2019-11-22 15:59:36 +00:00
David Baker adac0c353c Fix calls in e2e rooms
Events will be decrypted after the sync event, so we were having
to wait until the next sync event before they got processed.
2019-11-22 15:56:06 +00:00
Michael Telatynski 04fca16420 Stop using Bluebird promise::value 2019-11-22 15:36:42 +00:00
Bruno Windels ca89b6e7a8 use adapter for to_device requests to have same api as for verif over DM
Riot doesn't fully implement to_device verifications, e.g.
it doesn't send a `request` but immediately sends a `start` event.

Because of this, `crypto.verification.request` doesn't get fired,
as that code path doesn't get triggered. This is why MatrixChat
in the react-sdk was listening for `crypto.verification.start`.

Verification over DM *does* send a `request` event first, so
to have the same API for both methods, we fake the request and
wrap the verifier in it.
2019-11-22 16:12:19 +01:00
Bruno Windels ac1173c628 also emit crypto.verification.request for verification over DM 2019-11-22 16:11:49 +01:00
Michael Telatynski 0a0ae111f6 replace Bluebird::map 2019-11-22 15:03:03 +00:00
David Baker 71a6e015f4 Merge pull request #1084 from matrix-org/dbkr/remove_check
Remove 'check' npm script
2019-11-22 10:53:48 +00:00
David Baker e8bbb8a1cc Remove 'check' npm script
...whose only purpose was to run the tests without coverage because
the coverage tool was awful and ruined all the line numbers (moreso).
2019-11-21 19:30:46 +00:00
David Baker 04764998cb Merge pull request #1083 from matrix-org/dbkr/advanced_anti_chirp
Always process call events in batches
2019-11-21 19:22:24 +00:00
David Baker 5262d716e4 Lint 2019-11-21 19:10:27 +00:00
David Baker 7addacba38 Always process call events in batches
We had a bunch of logic in place to suppress calls if the answer
or hangup had already arrived, but we only used it on startup.
This extends this logic to happen all the time, which means we'll
also do the same suppression if a call happenned while we were
offline.
2019-11-21 18:56:37 +00:00
David Baker 8f8c9c8ec0 Merge pull request #1082 from matrix-org/dbkr/death_to_the_reload_chirp
Fix ringing chirp on loading
2019-11-21 18:20:20 +00:00
David Baker 3a9832a8c6 Fix ringing chirp on loading
We have a heap of logic to do the right thing when a call event
arrives, eg. wait until the client is ready so we can see if there's
already been a hangup event before saying there's an incoming call.

Unfortunately it only waited until the client was prepared, not
until it was syncing, so any events that arrived from the server
in the catchup sync bypassed this logic altogether.

This was probably broken back when we introduced the sync accumulator,
since before then, "PREPARED" meant, "done initialsync" rather than
"loaded fake initialsync from storage".

Fixes https://github.com/vector-im/riot-web/issues/3572
2019-11-21 17:56:04 +00:00
Bruno Windels 4a40c10d4c add helper method on event for current age according to local clock 2019-11-21 17:13:43 +01:00
Michael Telatynski 58f8ca7d66 Merge pull request #1081 from matrix-org/t3chguy/remove_bluebird_2
Remove *most* bluebird specific things
2019-11-21 11:34:19 +00:00
Michael Telatynski 4d950fec66 fixxy
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-21 10:59:18 +00:00
Michael Telatynski b4f68f4fc6 Stop using Bluebird promise::nodeify
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-21 10:43:58 +00:00
Michael Telatynski ac742aad70 use Bluebird in promise utils sleep so it has .done and .nodeify
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-21 10:14:06 +00:00
Michael Telatynski 53d225a1d1 fix stub timelineSet for timeline-window.spec
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-21 10:09:36 +00:00
Michael Telatynski 549b0f9313 Stop using Bluebird.delay and Bluebird promise::delay
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-21 10:05:59 +00:00
Michael Telatynski 2ce106382a Stop using Bluebird promise::isFulfilled()
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-21 10:01:16 +00:00
Michael Telatynski b44f43e5db fix import in relations.js
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-21 09:45:00 +00:00
Michael Telatynski 2321b9a04e Merge pull request #1080 from matrix-org/t3chguy/jest
Switch to Jest
2019-11-20 22:03:25 +00:00
Michael Telatynski 3bd518cf7f update buildkite pipeline name
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-20 21:59:50 +00:00
Michael Telatynski c57109c2f3 tidy up
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-20 21:16:11 +00:00
Michael Telatynski 522640edd9 rip out lolex also
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-20 21:05:21 +00:00
Michael Telatynski 5fc0629201 fix expect calls
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-20 20:19:54 +00:00
Michael Telatynski 26edd7431a fix yarn scripts test
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-20 19:56:50 +00:00
Michael Telatynski fd58957b06 migrate to jest from mocha+expect+istanbul
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-20 19:52:50 +00:00
RiotRobot 12bb0b86dd v2.4.4-rc.1 2019-11-20 18:19:30 +00:00
RiotRobot 165c1fc0b6 Prepare changelog for v2.4.4-rc.1 2019-11-20 18:19:30 +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
Bruno Windels c785b10603 Merge pull request #1077 from matrix-org/bwindels/dm-verif-in-e2ee-rooms
Fix SAS verification in encrypted DMs
2019-11-19 14:32:01 +00:00
Bruno Windels 90512bdd5f also listen for non-encrypted events when verifying over DM 2019-11-19 15:23:05 +01:00
J. Ryan Stinnett 4acd06eaba Fix typo in CrossSigning#resetKeys 2019-11-19 13:52:26 +00:00
J. Ryan Stinnett 10751e9a6d Merge pull request #1078 from matrix-org/jryans/4s-new-key-backup
Cross-signing / secret storage tweaks
2019-11-19 12:54:38 +00:00
J. Ryan Stinnett d2ebc58c3c Use secret storage alg const in tests 2019-11-19 11:46:08 +00:00
J. Ryan Stinnett d51c5a2d68 Rename secret storage file to match the default class 2019-11-19 11:21:20 +00:00
J. Ryan Stinnett 1f24845431 Standardise naming of key ID variables in secret storage
Keys in secret storage have both an ID and an optional name, but most `keyName`
variables were actually storing the ID value. This renames and standardises to
avoid confusion.
2019-11-19 10:48:47 +00:00
Bruno Windels 3b02b62ba5 add m.relates_to back to the content on the requesting side for e2e room
as it needs to be added to the commitment hash
as before, getContent() in an e2ee room doesn't return the relation
2019-11-18 18:34:05 +01:00
Bruno Windels 24ae787736 make it explicit that the transaction id is added for the start event
as it should included in the commitment hash
2019-11-18 18:33:11 +01:00
Bruno Windels cd735ef459 use getRelation as getContent()[m.relates_to] doesn't work in e2ee rooms 2019-11-18 18:31:39 +01:00
Bruno Windels 180fea8ace only send decrypted events to Verifier in e2ee rooms 2019-11-18 18:30:43 +01:00
J. Ryan Stinnett 5f02c4b5ad Namespace default secret storage key methods 2019-11-18 15:19:18 +00:00
David Baker 41680f6089 Merge pull request #1075 from matrix-org/dbkr/fix_key_backup_local_trust
Fix local trust for key backups
2019-11-18 14:57:35 +00:00
J. Ryan Stinnett 730f7d3dff Extract secret storage algorithm to constant 2019-11-18 14:38:02 +00:00
David Baker d32033f105 Merge remote-tracking branch 'origin/develop' into dbkr/fix_key_backup_local_trust 2019-11-18 14:10:04 +00:00
David Baker 440274d639 Fix local trust for key backups
https://github.com/matrix-org/matrix-js-sdk/pull/832 added
cross-signing checks for backup trust but we failed to merge in the
check for the the backup being trusted locally.

Fixes https://github.com/vector-im/riot-web/issues/11404
2019-11-18 13:30:00 +00:00
J. Ryan Stinnett f93130a8a7 Add method to check whether cross-signing has keys 2019-11-18 12:32:39 +00:00
Bruno Windels 3d9bddfb9f Merge pull request #1072 from matrix-org/bwindels/userinfomakeover
Add method to get last active timestamp in room
2019-11-15 16:39:31 +00:00
Travis Ralston 439abbcce9 Merge pull request #1071 from matrix-org/travis/new-deactivate
Check the right Synapse endpoint for determining admin capabilities
2019-11-15 09:17:12 -07:00
David Baker ac91367801 Merge pull request #832 from matrix-org/dbkr/cross_signing
Cross Signing Support
2019-11-15 16:09:06 +00:00
David Baker 2a63cc474c Update import 2019-11-15 15:57:25 +00:00
David Baker 56261263f5 Rename backup_password & functions
Not Just For Backups Anymore
2019-11-15 15:54:43 +00:00
David Baker 04b57bbe9d Remove ghost of some old code 2019-11-15 15:27:02 +00:00
David Baker c550f83a04 update jsdoc 2019-11-15 14:57:29 +00:00
David Baker 5224ef4b1f This is now implemented
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-15 14:56:05 +00:00
David Baker 2ab033e76e is now implemented 2019-11-15 14:45:43 +00:00
David Baker fa2e669eda More jsdoc updates
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-15 14:44:08 +00:00
David Baker f0ba1f2ac0 c+p fail
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-15 14:43:41 +00:00
David Baker 6d0237ec71 This now returns DeviceTrustLevel too 2019-11-15 14:42:26 +00:00
David Baker 97dff4640a Capitalise jsdoc
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-15 14:41:12 +00:00
David Baker 00b571a429 c+p fail
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-15 14:40:57 +00:00
David Baker 86e0f49231 c+p fail
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-15 14:40:16 +00:00
David Baker f2f205f9bd Typo
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-15 14:38:44 +00:00
David Baker f84ec090cb backticks in jsdoc
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-15 14:38:27 +00:00
David Baker d37ed9ff6f lint 2019-11-15 12:39:14 +00:00
David Baker f5a5f5e51a Update yarn.lock 2019-11-15 12:31:22 +00:00
David Baker fe010242d9 Why is 'cross-signing' so hard to type?
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-15 12:30:05 +00:00
David Baker 545ebf81bf Move Crypto.prototype.init back to its rightful place 2019-11-15 12:29:03 +00:00
David Baker 408934932a copy jsdoc to internal methods 2019-11-15 12:27:14 +00:00
David Baker 6f42824c35 Typo
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-15 12:26:24 +00:00
David Baker c3215d51bd Switch the CroosSigningLevel constants
we check in resetKeys and set all if it's & 4 anyway, so may as well
make the constants a normal bitmask and then we can use the MASTER
constant below.
2019-11-15 12:23:37 +00:00
David Baker e541b96a71 Change check{User|Device}Trust interfaces
...to return objects with functions rather than a bitmask
2019-11-15 12:15:13 +00:00
Travis Ralston 904a2f466e Maybe use the right user ID too 2019-11-14 11:54:13 -07:00
Travis Ralston bad48da11a Check the right Synapse endpoint for determining admin capabilities 2019-11-14 11:50:06 -07:00
David Baker ce2d1d6e2b Don't emit event here, as per comment 2019-11-14 17:41:58 +00:00
Bruno Windels 2820071db1 add method to get last active timestamp in room 2019-11-14 17:00:28 +01:00
David Baker 5937185ce9 Assert usage of setDeviceVerification for cross-signing keys
We can't mark a cross-signing key as blocked/unblocked, known/unknown
or unverified, so throw an exception instead of doing nothing.

Also comment what's going on in this function.
2019-11-14 14:24:41 +00:00
David Baker be9b7a0d24 Remove getPublicKey
which was the same as getId
2019-11-14 14:10:13 +00:00
David Baker 7ca09ad749 tariling space 2019-11-14 12:18:07 +00:00
David Baker 686a7a40f9 Remove outdated comment
uhoreg says this is fine now...
2019-11-14 12:11:07 +00:00
David Baker 2a7b2835b6 remove unused function 2019-11-14 12:11:07 +00:00
David Baker 69ecf3b145 jsdoc formatting 2019-11-14 12:11:07 +00:00
David Baker 2cd748b50c Add matrix foundation copyright
The creation of this file just predates matrix.org foundation so it
should have both
2019-11-14 12:11:07 +00:00
David Baker 291133beb9 Fix comment 2019-11-14 12:11:07 +00:00
David Baker e10c17c866 Use official name for SSSS
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-14 12:10:56 +00:00
David Baker 0048cbef08 Mark cross siging / SSSS APIs as unstable
also add missing jsdoc
2019-11-14 11:51:44 +00:00
David Baker d9d65309b3 More s/cross-signing/crossSigning/ 2019-11-14 11:29:08 +00:00
David Baker d5d8032b5b Camelcase event names
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-14 11:04:37 +00:00
David Baker 693c749da0 lint 2019-11-13 17:59:25 +00:00
David Baker 7218e31a9c Sign & verify SSSS keys 2019-11-13 17:52:24 +00:00
David Baker 1798f3921f Make setDeafultKeyId wait for event 2019-11-13 14:42:08 +00:00
David Baker d12c56a623 lint 2019-11-13 14:11:50 +00:00
David Baker 26aa3d3ce7 Support default keys 2019-11-13 14:09:40 +00:00
David Baker c97a87d1f6 Throw if an unknown key is specified
It's probably important that the app knows if a secret isn't going
to be stored under one or more of the keys it thought it was going
to be stored under.

Also add a test to assert it.
2019-11-12 14:07:05 +00:00
David Baker 9bc185d459 Fix what was probablyt a c+p fail 2019-11-12 13:21:37 +00:00
David Baker 4c651c15ea Convert secrets events to callbacks too 2019-11-11 20:01:11 +00:00
David Baker a98e6964ef Missed bits of callback renaming 2019-11-11 16:51:49 +00:00
David Baker 6f8d9c4693 Rename getPrivateKeys to getCrossSigningKeys 2019-11-11 16:45:01 +00:00
David Baker fbc4bd0c96 Merge pull request #1067 from matrix-org/bwindels/verification-over-dm
Support for verification requests in the timeline
2019-11-08 16:19:50 +00:00
J. Ryan Stinnett 03c9241783 Merge pull request #1066 from matrix-org/jryans/privacy-prefix-r0
Use stable API prefix for 3PID APIs when supported
2019-11-07 20:00:57 +02:00
David Baker 3a983271d6 add comments 2019-11-07 16:21:53 +00:00
David Baker 03fe4afe32 lint 2019-11-07 15:20:07 +00:00
David Baker 12627022d1 Convert sas verification test to callbacks 2019-11-07 15:18:16 +00:00
David Baker fabfe16d45 lint 2019-11-07 12:35:39 +00:00
David Baker a34758f938 Convert event interface to callbacks
Use options.cryptoCallbacks for things that require information
from the app rather than events, since events can have zero, one
or many listeners and the emitter doesn't know how many, so if
nobody's listening then we would have just waited forever for a
response.

Also a collection of other changes like renaming 'fu' to 'firstUse'
2019-11-07 12:31:44 +00:00
J. Ryan Stinnett 20f5c3ea28 Use stable API prefix for 3PID APIs when supported
If the server advertises spec version r0.6.0, it must have the 3PID APIs
available under the stable API prefix.

Fixes https://github.com/vector-im/riot-web/issues/11246
2019-11-06 18:02:10 +00:00
Bruno Windels 62e490cfe4 add FIXME note for (expected) uncaught rejection 2019-11-06 12:36:50 +01:00
Bruno Windels a9dba39623 include redacted event so has same signature as other Relations events 2019-11-04 15:56:15 +01:00
Bruno Windels f1d417597c only emit Event.relationsCreated once event has been added
so if we ask for the relations in the handler, we don't get
an empty result
2019-11-04 15:55:29 +01:00
RiotRobot 549f679bf1 Merge branch 'master' into develop 2019-11-04 13:59:24 +00:00
RiotRobot 6ba052dcc4 v2.4.3 2019-11-04 13:51:50 +00:00
RiotRobot de873b84f5 Prepare changelog for v2.4.3 2019-11-04 13:51:49 +00:00
Bruno Windels 37558ac1b4 detect other end cancelling and reject main promise 2019-11-04 14:43:44 +01:00
Bruno Windels 9140d5a091 don't clear expected type before including it in error 2019-11-04 14:37:12 +01:00
J. Ryan Stinnett 7827af0d90 Merge pull request #1063 from matrix-org/jryans/rm-jenkins
Remove Jenkins scripts
2019-11-01 18:23:16 +02:00
J. Ryan Stinnett 1af8d20adf Remove Jenkins scripts
We haven't used Jenkins for a while, so it seems safe to remove the scripts that
supported it.
2019-11-01 16:08:29 +00:00
J. Ryan Stinnett 91df096698 Update tests 2019-11-01 14:10:16 +00:00
J. Ryan Stinnett e8fd0498a7 Relax identity server discovery checks to FAIL_PROMPT
As discussed in MSC2284, this relaxes the identity server discovery to a
`FAIL_PROMPT` state so that clients can choose to warn and continue.

Part of https://github.com/vector-im/riot-web/issues/11102
Implements https://github.com/matrix-org/matrix-doc/pull/2284
2019-11-01 11:24:51 +00:00
David Baker f3073e120d Space 2019-11-01 10:51:49 +00:00
David Baker a571624e13 Typo 2019-11-01 10:46:43 +00:00
David Baker 74b649c04c Typo 2019-11-01 10:45:41 +00:00
ZengJing 7973b99f50 Fix incorrect return value of MatrixClient.prototype.uploadKeys
Signed-off-by: Zeng Jing <beaclnd92@gmail.com>
2019-11-01 09:25:50 +08:00
RiotRobot e8f5a8b89d v2.4.3-rc.1 2019-10-30 16:40:25 +00:00
RiotRobot 2d0bda933c Prepare changelog for v2.4.3-rc.1 2019-10-30 16:40:25 +00:00
David Baker 49588da73d Fix more tests 2019-10-29 19:39:31 +00:00
David Baker 3e2d845342 Merge remote-tracking branch 'origin/develop' into dbkr/cross_signing 2019-10-28 16:47:16 +00:00
David Baker e92d2bd70a Fix test again
That one was part of the protocol - don't camelcase that
2019-10-28 16:07:26 +00:00
David Baker de1b545df1 lint 2019-10-28 15:42:42 +00:00
David Baker 3bec28b2ff make other tests pass 2019-10-28 15:23:58 +00:00
David Baker 8cad116dd7 Make tests pass
* Pass the http backend out of makeTestClients so we can tell it
   to expect queries and flush requests out
 * Change colons to dots in the key events
2019-10-28 14:56:35 +00:00
Hubert Chathi 35adb75d80 Merge pull request #1056 from uhoreg/fix_logger_path
fix the path in references to logger.js
2019-10-26 13:45:24 -04:00
Hubert Chathi e9908b1d97 fix the path in references to logger.js 2019-10-25 23:24:30 -04:00
Hubert Chathi fffd2eb70a Merge pull request #1050 from uhoreg/verification_in_dms
verification in DMs
2019-10-23 12:09:52 -04:00
Hubert Chathi 136b9c0f50 remove unnecessary async 2019-10-23 12:03:17 -04:00
Hubert Chathi 0f1206b4ee apply suggestions from review 2019-10-22 13:29:24 -04:00
RiotRobot 46d7e4c707 Merge branch 'master' into develop 2019-10-18 14:38:58 +01:00
RiotRobot c874783742 v2.4.2 2019-10-18 14:36:57 +01:00
RiotRobot bb296f50d9 Prepare changelog for v2.4.2 2019-10-18 14:36:57 +01:00
Michael Telatynski da68b53ff9 Merge pull request #1054 from rcsm/develop
Properly document the function possible returns for getRelationsForEvent
2019-10-17 15:35:33 +01:00
Rafael Cascalho bbe141d44e chore: updated throws docs in function getRelationsForEvent 2019-10-17 11:30:41 -03:00
Rafael Cascalho 8a03e41a7c chore: corrected the docs the getRelationsForEvent function 2019-10-17 10:51:34 -03:00
Rafael Cascalho a79e1bc976 Merge remote-tracking branch 'upstream/develop' into develop 2019-10-17 10:15:10 -03:00
J. Ryan Stinnett 056bfbf7a3 Merge pull request #1055 from matrix-org/jryans/bluebird-3.5
Downgrade to Bluebird 3.5.5 to fix Firefox
2019-10-17 00:10:54 +02:00
J. Ryan Stinnett e0b64a487d Downgrade to Bluebird 3.5.5 to fix Firefox
Bluebird 3.6.0+ currently breaks in at least Firefox with errors in their
rejection tracking approach. For now, this retreats back to the version we had
used for a long time.

Regressed by https://github.com/matrix-org/matrix-js-sdk/pull/1053
Fixes https://github.com/vector-im/riot-web/issues/11148
2019-10-17 00:05:47 +02:00
Rafael Cascalho d47d1d8f26 Merge remote-tracking branch 'upstream/develop' into develop 2019-10-15 11:12:00 -03:00
Rafael Cascalho 42a07de9a7 chore: corrected getRelationForEvent return docs 2019-10-15 10:28:23 -03:00
J. Ryan Stinnett aead855470 Merge pull request #1053 from matrix-org/jryans/major-deps-2
Upgrade safe deps to latest major version
2019-10-11 13:53:58 +01:00
J. Ryan Stinnett 335b2314f1 Upgrade safe deps to latest major version 2019-10-11 12:44:36 +01:00
Travis Ralston 89bab24c14 Merge pull request #1052 from clokep/no-js-import
Don't include .js in the import string.
2019-10-10 00:14:52 +01:00
Patrick Cloke 3a439dcdad Don't include .js in the import string.
Signed-off-by: Patrick Cloke <clokep@patrick.cloke.us>
2019-10-09 18:44:41 -04:00
RiotRobot 20d82eb92f v2.4.2-rc.1 2019-10-09 16:49:43 +01:00
RiotRobot 319e1d1191 Prepare changelog for v2.4.2-rc.1 2019-10-09 16:49:42 +01:00
Hubert Chathi 5f3492dbf8 send the m.key.verification.done message when done 2019-10-09 11:13:32 -04:00
David Baker 107c8c0b1f Merge pull request #1047 from matrix-org/dbkr/olm_session_describe
Log state of Olm sessions
2019-10-09 15:48:54 +01:00
David Baker 8c6d9586bf Update docs to reflect minimum olm version 2019-10-09 15:41:34 +01:00
David Baker 1271fc6bf3 Actually bump olm 2019-10-09 15:38:47 +01:00
David Baker c9df03c40c Bump to olm 3.1.4 which has olm_session_describe 2019-10-09 10:19:13 +01:00
Hubert Chathi d8e8dddd25 initial implementation of verification in DMs 2019-10-08 15:44:51 -04:00
Bruno Windels 27f6745123 Merge pull request #1048 from matrix-org/bwindels/redact-all-more-robust
Add method to get access to all timelines
2019-10-08 06:58:13 +00:00
Bruno Windels 964f448334 moar doc fix 2019-10-02 15:30:28 +02:00
Bruno Windels 20ee03bb44 fix docs 2019-10-02 15:25:19 +02:00
Bruno Windels 77bd677182 add method to get access to all timelines 2019-10-02 10:00:11 +02:00
RiotRobot e024d047e3 v2.4.1 2019-10-01 11:30:13 +01:00
RiotRobot 40943edc06 Prepare changelog for v2.4.1 2019-10-01 11:30:12 +01:00
David Baker e6699c5424 Log state of Olm sessions
...whenever we encrypt or decrypt a message on them. This adds
another line of logging for every device in the room, so will
be reasonably verbose if you're in large encrypted rooms, but
the information ought to be valuable.

Requires https://gitlab.matrix.org/matrix-org/olm/merge_requests/9

Don't merge before a new version of Olm is released with this merge
request (it won't work).
2019-10-01 11:19:52 +01:00
J. Ryan Stinnett bd8a307e50 Merge pull request #1046 from matrix-org/jryans/deps-2019-09-27
Upgrade deps
2019-09-27 16:01:50 +01:00
J. Ryan Stinnett f71301cafc Upgrade deps 2019-09-27 15:04:21 +01:00
RiotRobot 562bf9331b Merge branch 'master' into develop 2019-09-27 11:44:14 +01:00
RiotRobot 11e6eb94b5 v2.4.0 2019-09-27 11:42:17 +01:00
RiotRobot cee3aa2a7a Prepare changelog for v2.4.0 2019-09-27 11:42:17 +01:00
David Baker 81e3783488 Merge pull request #1043 from matrix-org/dbkr/ignore_empty_crypto_event
Ignore crypto events with no content
2019-09-27 10:44:17 +01:00
J. Ryan Stinnett fc7f9786f8 Merge pull request #1045 from matrix-org/jryans/yarn-cache-clean
Clean Yarn cache during release
2019-09-26 11:37:18 +01:00
J. Ryan Stinnett 0808c0edf1 Clean Yarn cache during release
Always run `yarn cache clean` during the `dist` step to workaround a Yarn bug
with Git commit package dependencies.
2019-09-26 11:18:20 +01:00
RiotRobot 8de6746efd v2.4.0-rc.1 2019-09-25 17:14:17 +01:00
RiotRobot eb9b8ef7c6 Prepare changelog for v2.4.0-rc.1 2019-09-25 17:14:16 +01:00
J. Ryan Stinnett b09621b915 Merge pull request #1044 from matrix-org/jryans/rm-id-server-creds
Remove id_server from creds for interactive auth
2019-09-25 16:02:41 +01:00
J. Ryan Stinnett 8d667f9367 Remove id_server from creds for interactive auth
For HSes that do not require an IS, we can remove `id_server` from the auth
params.

Fixes https://github.com/vector-im/riot-web/issues/10959
2019-09-25 14:59:32 +01:00
David Baker 56dfe6630f Ignore crypto events with no content 2019-09-25 12:05:52 +01:00
J. Ryan Stinnett 8b3b181a48 Merge pull request #1041 from matrix-org/jryans/rm-id-params-request-token
Remove IS details from requestToken to HS
2019-09-23 14:43:03 +01:00
J. Ryan Stinnett c952768542 Remove IS details from requestToken to HS
This removes the IS details (server and access token) from `requestToken` calls
to the HS, as long as the HS supports the new separate add and bind mode. In
this mode, all of the 3PID validation is handled by the HS, so the IS details
are not used.

Fixes https://github.com/vector-im/riot-web/issues/10933
2019-09-23 13:36:07 +01:00
J. Ryan Stinnett 1a368aa996 Merge pull request #1040 from matrix-org/jryans/msisdn-submit-url
Add support for sending MSISDN tokens to alternate URLs
2019-09-23 13:21:28 +01:00
J. Ryan Stinnett 61449458cf Add support for sending MSISDN tokens to alternate URLs
Part of https://github.com/vector-im/riot-web/issues/10923
2019-09-23 12:20:28 +01:00
J. Ryan Stinnett 4eb547e535 Merge pull request #1038 from matrix-org/jryans/msc2290
Add separate 3PID add and bind APIs
2019-09-20 14:37:00 +01:00
J. Ryan Stinnett b54acffaef Tweak unbind 3PID params 2019-09-19 17:28:50 +01:00
J. Ryan Stinnett 65a1833e1f Add 3PID unbind API 2019-09-19 15:28:58 +01:00
J. Ryan Stinnett 1ce4f25811 Use unstable prefix for add and bind 2019-09-19 15:28:35 +01:00
J. Ryan Stinnett 3127105516 Merge pull request #1037 from matrix-org/dependabot/npm_and_yarn/eslint-utils-1.4.2
Bump eslint-utils from 1.4.0 to 1.4.2
2019-09-18 12:52:28 +01:00
dependabot[bot] d59ea4be78 Bump eslint-utils from 1.4.0 to 1.4.2
Bumps [eslint-utils](https://github.com/mysticatea/eslint-utils) from 1.4.0 to 1.4.2.
- [Release notes](https://github.com/mysticatea/eslint-utils/releases)
- [Commits](https://github.com/mysticatea/eslint-utils/compare/v1.4.0...v1.4.2)

Signed-off-by: dependabot[bot] <support@github.com>
2019-09-18 11:48:29 +00:00
J. Ryan Stinnett f256f04440 Add MSISDN validation API on the IS
This API has existed for quite a while, but historically we've instead proxied
this request via the homeserver. As part of MSC2290 work, we are changing
approaches such that we will request tokens directly from the IS when binding
for discovery.

Part of https://github.com/vector-im/riot-web/issues/10839
2019-09-18 11:47:37 +01:00
J. Ryan Stinnett b444aaa67e Add separate add and bind HS APIs
Part of https://github.com/vector-im/riot-web/issues/10839
2019-09-18 11:16:23 +01:00
J. Ryan Stinnett 745185e689 Fix function doc syntax 2019-09-18 10:38:52 +01:00
J. Ryan Stinnett 2bfa891f0a Add function to check for separate 3PID add and bind
This adds a way to test for MSC2290 support on the homeserver with separate add
and bind functions. It checks the unstable feature flag as well as the upcoming
r0.6.0 spec version.

Part of https://github.com/vector-im/riot-web/issues/10839
2019-09-18 10:36:41 +01:00
Travis Ralston 147167bed3 Merge pull request #1036 from matrix-org/travis/wrap-ff
Handle WebRTC security errors as non-fatal
2019-09-17 13:52:19 -06:00
Travis Ralston 565e18e8a3 Handle WebRTC security errors as non-fatal
Fixes https://github.com/vector-im/riot-web/issues/10898

In some restricted modes of Firefox, the WebRTC classes aren't super available: accessing them can cause SecurityErrors to be raised. In these conditions, we should just disable WebRTC support instead of falling apart.
2019-09-17 13:49:50 -06:00
Travis Ralston 55b4595bbf Merge pull request #1035 from matrix-org/travis/r0.6
Check for r0.6.0 support in addition to unstable feature flags
2019-09-16 14:43:00 -06:00
Travis Ralston eeb2c463dc Check for r0.6.0 support in addition to unstable feature flags
To avoid the same problem that happened with lazy-loading (see https://github.com/matrix-org/synapse/issues/5528).

Note that as of writing r0.6.0 is not yet released, however it is the next scheduled release of the client-server API.
2019-09-16 14:30:18 -06:00
RiotRobot d9bb0e9a52 Merge branch 'master' into develop 2019-09-16 17:43:01 +01:00
RiotRobot 7d07ab7c7e v2.3.2 2019-09-16 17:40:19 +01:00
RiotRobot f8ff3aac58 Prepare changelog for v2.3.2 2019-09-16 17:40:19 +01:00
David Baker 299a7728d1 Merge pull request #1034 from matrix-org/travis/t2
[Release] Fix addPendingEvent with pending event order == chronological
2019-09-16 17:02:32 +01:00
David Baker 39dc6a1742 Fix addPendingEvent with pending event order == chronological
When the pending event order setting was set to 'chronological'
(the default) `addPendingEvent` would NPE because Room no longer
has a `this._filter` property. It should get the filter from the
event timeline set instead, as it does in the previous line when
checking or the presence of a filter.

We should strongly consider changing the default pending event order
to 'detached' and probably removing 'chronological' or comitting to
support it properly: it's not really tested and is prone to breakage
like this.

Applies flumpt's fix from https://github.com/matrix-org/matrix-js-sdk/issues/599
Fixes https://github.com/matrix-org/matrix-js-sdk/issues/599
2019-09-16 09:36:14 -06:00
RiotRobot f21c5aa7f2 v2.3.2-rc.1 2019-09-13 16:13:27 +01:00
RiotRobot e9bc3f26a5 Prepare changelog for v2.3.2-rc.1 2019-09-13 16:13:27 +01:00
David Baker 23eaddd6ea Merge pull request #1033 from matrix-org/travis/t1
Synapse admin functions to release
2019-09-13 16:07:58 +01:00
Travis Ralston 8143ce8450 Update src/client.js
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-09-13 09:01:16 -06:00
Travis Ralston 0a487ec43e Add Synapse admin functions for deactivating a user
For https://github.com/matrix-org/matrix-react-sdk/pull/3371
2019-09-13 09:01:05 -06:00
Travis Ralston 0edb483802 Merge pull request #1032 from matrix-org/travis/t1
[To Release] Add matrix base API to report an event
2019-09-13 08:27:31 -06:00
Michael Telatynski 06a32ce0a1 Add matrix base API to report an event
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-09-13 08:24:49 -06:00
RiotRobot 8cae00407a Merge branch 'master' into develop 2019-09-12 12:51:29 +01:00
RiotRobot a57ec87c67 v2.3.1 2019-09-12 12:48:36 +01:00
RiotRobot 4e62491ea4 Prepare changelog for v2.3.1 2019-09-12 12:48:36 +01:00
RiotRobot 5758029c1e v2.3.1-rc.1 2019-09-11 18:38:22 +01:00
RiotRobot 8f08710c58 Prepare changelog for v2.3.1-rc.1 2019-09-11 18:38:21 +01:00
David Baker 90f98105f0 Merge pull request #1031 from matrix-org/dbkr/update_profile_on_redact_2
Update room members on member event redaction
2019-09-11 18:12:46 +01:00
David Baker 90354aa330 Update room members on member event redaction
If a member event was redacted, we weren't updating the current
state.
2019-09-11 18:09:54 +01:00
David Baker aaabebe7f5 Merge pull request #1030 from matrix-org/dbkr/update_profile_on_redact
Update room members on member event redaction
2019-09-11 18:07:49 +01:00
David Baker 80a92dcdc2 Update room members on member event redaction
If a member event was redacted, we weren't updating the current
state.
2019-09-11 16:07:34 +01:00
Travis Ralston dc9081e9d4 Merge pull request #1028 from matrix-org/travis/hidden_rr
Support hidden read receipts
2019-09-10 10:55:58 -06:00
Travis Ralston 3c299637b6 Merge pull request #1029 from matrix-org/travis/lowercase-lookups
Do 3pid lookups in lowercase
2019-09-09 15:19:48 -06:00
Travis Ralston 07af333943 clarify comment 2019-09-09 14:44:51 -06:00
Travis Ralston 0bbc781d0c Do 3pid lookups in lowercase
Fixes https://github.com/vector-im/riot-web/issues/10754
2019-09-07 14:04:30 -06:00
Travis Ralston 79bf64f079 Appease the linter 2019-09-05 20:40:16 -06:00
Travis Ralston ed67d39456 Support hidden read receipts 2019-09-05 19:38:49 -06:00
Travis Ralston 2f8cc75432 Merge pull request #1027 from matrix-org/travis/synapse_admin
Add Synapse admin functions for deactivating a user
2019-09-02 11:08:39 -06:00
Travis Ralston 03cccef805 Update src/client.js
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-09-02 11:06:54 -06:00
Travis Ralston 6d5a0c2718 Add Synapse admin functions for deactivating a user
For https://github.com/matrix-org/matrix-react-sdk/pull/3371
2019-09-01 18:05:12 -06:00
David Baker 42b359eb5c Merge pull request #1026 from matrix-org/dbkr/fix_add_pending_events_chronological
Fix addPendingEvent with pending event order == chronological
2019-08-28 17:02:20 -04:00
David Baker 3071587f11 Fix addPendingEvent with pending event order == chronological
When the pending event order setting was set to 'chronological'
(the default) `addPendingEvent` would NPE because Room no longer
has a `this._filter` property. It should get the filter from the
event timeline set instead, as it does in the previous line when
checking or the presence of a filter.

We should strongly consider changing the default pending event order
to 'detached' and probably removing 'chronological' or comitting to
support it properly: it's not really tested and is prone to breakage
like this.

Applies flumpt's fix from https://github.com/matrix-org/matrix-js-sdk/issues/599
Fixes https://github.com/matrix-org/matrix-js-sdk/issues/599
2019-08-28 16:34:09 -04:00
Hubert Chathi f3ec9768bc update to follow latest MSC 2019-08-27 16:53:36 -07:00
Travis Ralston 23159807b0 Merge pull request #1024 from matrix-org/travis/wk-raw
Add AutoDiscovery.getRawClientConfig() for easy .well-known lookups
2019-08-27 08:22:55 -06:00
Travis Ralston b1ba9f76b8 Merge pull request #1025 from matrix-org/travis/fix-identity-json-error
Don't convert errors to JSON if they are JSON already
2019-08-23 12:14:58 -06:00
Travis Ralston 0e51dfed46 Don't convert errors to JSON if they are JSON already
For example, if the identity server throws a 401 on `/account`, we end up here with a JSON object. Don't convert the string `object Object` to JSON because it'll fail: just use the object.
2019-08-23 12:00:07 -06:00
J. Ryan Stinnett 09b00335f8 Merge pull request #1022 from matrix-org/jryans/is-token-to-hs
Send id_access_token to HS for use in proxied IS requests
2019-08-23 17:13:41 +01:00
J. Ryan Stinnett 3d274815d9 Change to provider object 2019-08-23 11:17:39 +01:00
Travis Ralston 70d60b905d Add AutoDiscovery.getRawClientConfig() for easy .well-known lookups
Useful in cases where you don't need to validate the homeserver information.
2019-08-22 14:45:42 -06:00
Travis Ralston 3e2ffb25a6 Merge pull request #1023 from matrix-org/travis/cleanup-identity-http
Clean up JSON handling in identity server requests
2019-08-22 10:55:08 -06:00
Travis Ralston 8b9bef5cb3 Clean up JSON handling in identity server requests 2019-08-22 08:32:20 -06:00
J. Ryan Stinnett 31e72efc91 Send id_access_token to HS for use in proxied IS requests
This passes along the `id_access_token` to the HS, which it will need when
speaking v2 IS APIs to the IS.

Unfortunately, some HSes seem to explode when given this new parameter, so we
only pass it along for the moment if an unstable feature `m.id_access_token` is
also set.

Part of https://github.com/vector-im/riot-web/issues/10525
Defined in MSC2140
2019-08-22 14:31:41 +01:00
Travis Ralston 60b7252597 Merge pull request #1021 from matrix-org/travis/v2-identity-lookups
Use the v2 (hashed) lookup for identity server queries
2019-08-22 07:05:16 -06:00
Travis Ralston 3980b62df2 js-doc syntax is unknown to our js-doc 2019-08-21 14:32:38 -06:00
Travis Ralston b306df726a Lookups are URL safe 2019-08-21 14:30:24 -06:00
Travis Ralston 3d5a79be3b Hashes need tokens too 2019-08-21 14:30:16 -06:00
Travis Ralston ba78d1a9ae JSON is JSON 2019-08-21 14:30:04 -06:00
Travis Ralston 241811298f Appease the js-doc 2019-08-21 14:17:55 -06:00
Travis Ralston 8a0ddc43ab Use the v2 (hashed) lookup for identity server queries
Fixes https://github.com/vector-im/riot-web/issues/10556
Implements [MSC2134](https://github.com/matrix-org/matrix-doc/pull/2134) with assistance from [MSC2140](https://github.com/matrix-org/matrix-doc/pull/2140) for auth.

Note: this also changes all identity server requests to use JSON as a request body. URL encoded forms were allowed in v1 but deprecated in favour of JSON. v2 APIs do not allow URL encoded forms.
2019-08-21 14:08:46 -06:00
David Baker 898fa0e41b Merge pull request #1018 from matrix-org/dbkr/getversions
Add getIdServer() & doesServerRequireIdServerParam()
2019-08-19 11:23:53 +01:00
David Baker 081ff4dec0 Merge pull request #1019 from matrix-org/dbkr/requesttoken_optional_id_server
Make requestToken endpoints work without ID Server
2019-08-19 11:23:05 +01:00
David Baker 3c69b8511d cache should expire TODO 2019-08-19 11:21:32 +01:00
David Baker 6843d86ecf Truthiness not null check
Co-Authored-By: Travis Ralston <travpc@gmail.com>
2019-08-16 19:41:37 +01:00
David Baker 2e91200136 lint
also WE FIXED SOME WARNINGS! Go us.
2019-08-16 18:13:42 +01:00
David Baker 852304c417 Make requestToken endpoints work without ID Server
Hopefully with doc in appropriate functions
2019-08-16 18:09:29 +01:00
David Baker ee752e3885 Add getIdServer() & doesServerRequireIdServerParam()
Remove individual cache for lazy loading and just cache the whole
versions response, then we can cache both of these flags
2019-08-16 15:02:49 +01:00
David Baker b9480e4302 Merge pull request #1016 from matrix-org/dbkr/fix_setidentityserver
Fix setIdentityServer
2019-08-15 15:11:54 +01:00
David Baker 2ae4d07971 Fix setIdentityServer 2019-08-15 12:01:40 +01:00
J. Ryan Stinnett 90cac8a118 Merge pull request #1015 from matrix-org/jryans/stun-turn-fallback
Change ICE fallback server and make fallback opt-in
2019-08-15 10:50:56 +01:00
J. Ryan Stinnett db18274f6e Revert no TURN servers event
The intended flow has changed so we prompt about this case in context of making
a call, so this event is no longer needed.
2019-08-14 14:09:05 +01:00
J. Ryan Stinnett 172bad8b55 Support configuring ICE fallback at client init time
This adds a new client init option to configure whether an ICE fallback server
is allowed to be used.
2019-08-14 13:44:50 +01:00
J. Ryan Stinnett dfe454e18f Change ICE server fallback to opt-in
This changes the ICE server fallback to be disabled by default. The SDK consumer
will receive a new event in case the homeserver has no ICE servers of its own,
and can prompt the user to agree to the fallback if desired.

Part of https://github.com/vector-im/riot-web/issues/10173
2019-08-13 15:55:31 +01:00
J. Ryan Stinnett 3d8dd29b4c Change STUN fallback to turn.matrix.org
This changes the STUN fallback server from a Google server to one hosted at
`turn.matrix.org`.

Part of https://github.com/vector-im/riot-web/issues/10173
2019-08-13 13:31:43 +01:00
J. Ryan Stinnett c3ff213ec9 Improve WebRTC object logging 2019-08-13 13:29:59 +01:00
J. Ryan Stinnett e80e5e1f8c Firefox supports urls on RTCIceServer
There's no longer a need to translate ICE server objects for Firefox, as it
supports the `urls` array since version 37.
2019-08-13 13:15:56 +01:00
David Baker bba249d5ce Merge pull request #1014 from matrix-org/dbkr/throw_if_no_id_url
Throw an exception if trying to do an ID server request with no ID server
2019-08-13 10:41:04 +01:00
David Baker f57df2bee5 Catch no ID server on other method 2019-08-12 16:06:37 +01:00
David Baker b930638156 Be more verbose
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-08-12 16:00:40 +01:00
David Baker 39c1de19fc Throw an exception if trying to do an ID server request with no ID server
Will help with https://github.com/vector-im/riot-web/issues/10540
2019-08-12 15:39:50 +01:00
David Baker 17724fc8d3 Merge pull request #1013 from matrix-org/dbkr/set_is
Add setIdentityServerUrl
2019-08-09 18:11:45 +01:00
David Baker 4c6d11d9ed Add setIdentityServerUrl
Hopefully fairly self explanatory. The ID server URL can be changed
fairly readily, whereas the HS URL would require a different access
token etc.
2019-08-09 18:05:37 +01:00
Michael Telatynski 05d77a85c9 Merge pull request #1011 from matrix-org/t3chguy/add-rooms-roomid-report-eventid
Add matrix base API to report an event
2019-08-08 15:21:20 +01:00
J. Ryan Stinnett e95a133cdd Merge pull request #1010 from matrix-org/jryans/user-settings-toggle-3pid
Fix POST body for v2 IS requests
2019-08-08 15:17:53 +01:00
Michael Telatynski c21382d721 Add matrix base API to report an event
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-08-08 15:16:11 +01:00
J. Ryan Stinnett 8c15125e23 Fix POST body for v2 IS requests
POST bodies for v2 IS requests must be sent as JSON.

Part of https://github.com/vector-im/riot-web/issues/10159
2019-08-08 10:29:14 +01:00
J. Ryan Stinnett 64ddbd97dd Merge pull request #1009 from matrix-org/jryans/user-settings-toggle-3pid
Add API for bulk lookup on the Identity Server
2019-08-07 18:05:58 +01:00
J. Ryan Stinnett 9c24bcb7a9 Tweak the type 2019-08-07 18:05:32 +01:00
J. Ryan Stinnett 8f016726f0 Add API for bulk lookup on the Identity Server
This adds support for querying `/bulk_lookup` on the IS to check several 3PIDs
at the same time.

Part of https://github.com/vector-im/riot-web/issues/10159
2019-08-07 17:45:13 +01:00
J. Ryan Stinnett 649fe7a490 Merge pull request #1000 from matrix-org/t3chguy/remove_authedRequestWithPrefix
Remove deprecated authedRequestWithPrefix and requestWithPrefix
2019-08-06 18:58:24 +01:00
RiotRobot 35f1cdf89c Merge branch 'master' into develop 2019-08-05 11:48:35 +01:00
RiotRobot 06adc34fb3 v2.3.0 2019-08-05 11:46:46 +01:00
RiotRobot 87bf07f95e Prepare changelog for v2.3.0 2019-08-05 11:46:46 +01:00
J. Ryan Stinnett f05bf3f845 Merge pull request #1007 from matrix-org/jryans/is-account-info
Add API for checking IS account info
2019-08-01 17:28:08 +01:00
Travis Ralston ab512d087c Merge pull request #1008 from matrix-org/travis/tombstone-push-rel
[release] Support rewriting push rules when our internal defaults change
2019-08-01 08:30:01 -06:00
Travis Ralston 6799c29921 Appease the tests 2019-08-01 08:23:19 -06:00
Travis Ralston a3f1da1981 Appease the linter 2019-08-01 08:23:19 -06:00
Travis Ralston 3b225651cc Support rewriting push rules when our internal defaults change
and change our internal default for tombstones
2019-08-01 08:23:19 -06:00
Travis Ralston a40d691159 Merge pull request #1006 from matrix-org/travis/tombstone-push
Support rewriting push rules when our internal defaults change
2019-08-01 08:22:47 -06:00
J. Ryan Stinnett 4ebe60b2ad Add API for checking IS account info
Part of https://github.com/vector-im/riot-web/issues/10452
2019-08-01 12:02:56 +01:00
J. Ryan Stinnett 5a70859593 Merge pull request #1005 from matrix-org/jryans/upgrade-deps-2019-07-31
Upgrade dependencies
2019-07-31 18:51:14 +01:00
Travis Ralston c7be810e65 Appease the tests 2019-07-31 11:00:44 -06:00
Travis Ralston 101217cfb6 Appease the linter 2019-07-31 11:00:38 -06:00
Travis Ralston 5c2aa4677f Support rewriting push rules when our internal defaults change
and change our internal default for tombstones
2019-07-31 10:52:44 -06:00
J. Ryan Stinnett ab9bfa68ae Upgrade dependencies 2019-07-31 17:36:35 +01:00
RiotRobot aa8c2ca277 v2.3.0-rc.1 2019-07-31 16:20:54 +01:00
RiotRobot 84509087ac Prepare changelog for v2.3.0-rc.1 2019-07-31 16:20:54 +01:00
J. Ryan Stinnett 2450d461fd Merge pull request #1002 from matrix-org/jryans/is-v2-auth
Add support for IS v2 API with authentication
2019-07-30 18:13:08 +01:00
J. Ryan Stinnett 50c590ae26 Note cleanup issue 2019-07-30 10:38:53 +01:00
J. Ryan Stinnett 516dff06ee Rename isAccessToken to identityAccessToken 2019-07-30 10:06:52 +01:00
Travis Ralston 9a8af05bfb Merge pull request #1001 from matrix-org/hs/recursive-tombstone-fixes
Tombstone bugfixes
2019-07-29 08:52:59 -06:00
Will Hunt c9bf61c387 Simplify Set 2019-07-29 15:29:18 +01:00
Will Hunt 4f0f2e8c16 Fix issues with recursive tombstones 2019-07-29 15:27:32 +01:00
J. Ryan Stinnett 6f042a2142 Add IS v2 support to other IS APIs
This adds v2 support with fallback to other IS APIs in the SDK.
2019-07-29 14:55:40 +01:00
J. Ryan Stinnett 91416bdbb2 Add IS v1 API fallback for lookup 2019-07-29 14:44:15 +01:00
J. Ryan Stinnett 9b093f7569 Add first pass of IS v2 API with authentication
This only updates the `/lookup` API so far. It also doesn't handle falling back
to v1.
2019-07-29 13:15:19 +01:00
Michael Telatynski b004d1602d Remove deprecated authedRequestWithPrefix and requestWithPrefix
replacing as documented with authedRequest

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-07-29 12:25:38 +01:00
David Baker 6cca73b999 Merge pull request #988 from matrix-org/dbkr/terms
Support for MSC2140 (terms of service for IS/IM)
2019-07-23 10:32:05 +01:00
David Baker fafd6df13e Use standard _matrix path for terms endpoints 2019-07-22 18:53:36 +01:00
RiotRobot 8f77870526 Merge branch 'master' into develop 2019-07-18 15:44:16 +01:00
RiotRobot eb0462e89b v2.2.0 2019-07-18 15:42:21 +01:00
RiotRobot 80748d7d85 Prepare changelog for v2.2.0 2019-07-18 15:42:20 +01:00
David Baker b694d53b73 Revert 8004e82c50
We need to switch the paths over all at once, so we can't commit
this yet: leave it until scalar suypports the new API then we can
update develop to use the _matrix paths.

(Also for some reason I broke the IS path too)
2019-07-15 14:26:16 +01:00
David Baker 8004e82c50 Use _matrix prefix for terms API 2019-07-15 13:51:39 +01:00
J. Ryan Stinnett 85b5849228 Upgrade lodash 2019-07-12 19:25:47 +01:00
J. Ryan Stinnett 6b86777e96 Upgrade lodash 2019-07-12 19:25:21 +01:00
RiotRobot efe64a4817 v2.2.0-rc.2 2019-07-12 17:25:20 +01:00
RiotRobot ad777f36b2 Prepare changelog for v2.2.0-rc.2 2019-07-12 17:25:20 +01:00
J. Ryan Stinnett de77ad867c Merge pull request #995 from matrix-org/jryans/v2.2.0/devices
Add a request method to /devices
2019-07-12 16:47:06 +01:00
Travis Ralston 9b35f86497 Add a request method to /devices
Turns out `HTTP /devices undefined` is invalid.

Regressed in https://github.com/matrix-org/matrix-js-sdk/pull/990
2019-07-12 16:38:43 +01:00
Travis Ralston 84fc8b1931 Merge pull request #994 from matrix-org/travis/fix-devices
Add a request method to /devices
2019-07-12 08:12:59 -06:00
RiotRobot 455c85fb69 v2.2.0-rc.1 2019-07-12 11:12:26 +01:00
RiotRobot 2a2fed695b Prepare changelog for v2.2.0-rc.1 2019-07-12 11:12:25 +01:00
Travis Ralston c1f28bd410 Add a request method to /devices
Turns out `HTTP /devices undefined` is invalid.

Regressed in https://github.com/matrix-org/matrix-js-sdk/pull/990
2019-07-11 14:35:04 -06:00
David Baker c74e0bb6b3 tell people what an IS/IM are 2019-07-11 16:29:27 +01:00
David Baker 5b9e158035 unused param
getTerms is un-authed so doesn't need the access token
2019-07-11 16:28:41 +01:00
Travis Ralston a8d200dd02 Merge pull request #993 from matrix-org/travis/sas-is-done
End the verification timer when verification is done
2019-07-11 08:20:30 -06:00
Travis Ralston 34ae967cb8 Merge pull request #990 from matrix-org/travis/stably-use-stable-apis
Stabilize usage of stably stable APIs (in a stable way)
2019-07-11 08:20:14 -06:00
RiotRobot a67f14825e Merge branch 'master' into develop 2019-07-11 10:36:44 +01:00
RiotRobot 50c14d0ab8 v2.1.1 2019-07-11 10:34:58 +01:00
RiotRobot 0edb6e6f6f Prepare changelog for v2.1.1 2019-07-11 10:34:58 +01:00
Travis Ralston 36d0dacda1 Process ephemeral events outside timeline handling 2019-07-11 10:21:44 +01:00
Bruno Windels 83b74070aa Merge pull request #987 from matrix-org/bwindels/include-orig-in-history
Expose original_event for /relations
2019-07-11 08:09:44 +00:00
Travis Ralston f80af68686 End the verification timer when verification is done
Fixes https://github.com/matrix-org/matrix-js-sdk/issues/980

This also improves cleanliness in the tests to cancel/terminate timers when needed.
2019-07-10 14:51:12 -06:00
Travis Ralston fe4ac06f43 Use the correct media endpoints 2019-07-10 13:24:11 -06:00
Travis Ralston eaaa3e980a Use unstable prefix for key backup 2019-07-10 13:17:31 -06:00
Travis Ralston 07629bfb9a unstable -> stable 2019-07-10 13:11:46 -06:00
Travis Ralston 88fdeca2bf Merge pull request #989 from matrix-org/travis/edu-timeline
Process ephemeral events outside timeline handling
2019-07-10 10:38:02 -06:00
Travis Ralston a3c8eac38b Process ephemeral events outside timeline handling 2019-07-10 10:26:21 -06:00
Bruno Windels bd5380c0b4 Merge pull request #986 from matrix-org/bwindels/include-ssa-for-replace
Don't accept any locally known edits earlier than the last known server-side aggregated edit
2019-07-10 14:38:26 +00:00
David Baker c3b5767999 update comment to reflect right version 2019-07-10 12:19:12 +01:00
David Baker 9e5c2732c9 consistent spacing 2019-07-10 12:18:41 +01:00
David Baker b8957fa917 omit null params 2019-07-10 12:17:52 +01:00
David Baker 52c139dcdc Forgot /terms for ISes
and IMs shouldn't have a slash
2019-07-10 12:15:31 +01:00
David Baker 39d4bf1494 SERVICE_TYPES 2019-07-10 12:08:13 +01:00
David Baker 4c713e3387 s/servicetypes/service-types/ 2019-07-10 11:53:59 +01:00
David Baker bb486f5148 SERVICE_TYPES
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-07-10 11:47:36 +01:00
David Baker 524fea1297 lint 2019-07-10 10:43:54 +01:00
David Baker e9528ebb98 Support for MSC2140 (terms of service for IS/IM) 2019-07-09 18:50:01 +01:00
Bruno Windels de18283c3b map, decrypt and return original_event if present 2019-07-09 17:52:58 +02:00
Bruno Windels cc1c7561a3 Merge pull request #984 from matrix-org/bwindels/replace-server-date
Get edit date transparently from server aggregations or local echo
2019-07-09 15:03:02 +00:00
Bruno Windels 5d928f07a0 don't accept any edit earlier than the server-side set edit. 2019-07-09 15:06:07 +02:00
Bruno Windels 01b882480f method to get edit date transparently from server aggregations or local echo 2019-07-09 11:29:19 +02:00
Travis Ralston 21d52fdbdd Merge pull request #982 from matrix-org/travis/soft-logout-keys
Add a function to flag keys for backup without scheduling a backup
2019-07-08 11:48:09 -06:00
Hubert Chathi 7f8b9de560 offer to upgrade device verifications to cross-signing 2019-07-08 12:26:00 -04:00
Hubert Chathi 761f22b63d minor cleanups 2019-07-08 12:25:28 -04:00
RiotRobot f9baff2a3a Merge branch 'master' into develop 2019-07-08 10:46:33 +01:00
RiotRobot 71eca4ffcc v2.1.0 2019-07-08 10:44:47 +01:00
RiotRobot bc8dca5105 Prepare changelog for v2.1.0 2019-07-08 10:44:47 +01:00
David Baker 3ae3dffff7 Lint 2019-07-08 10:37:24 +01:00
David Baker 81c6023940 Fix exception whilst syncing
event.getPushRules() may return null (for better or worse...).
Use client.getPushRulesForEvent which will calculate them if they
haven't already been calculated.

Fixes https://github.com/vector-im/riot-web/issues/10269
2019-07-08 10:37:24 +01:00
Travis Ralston 3a0f27fa7e Add a function to flag keys for backup without scheduling a backup
For https://github.com/vector-im/riot-web/issues/10263

Starting/scheduling the backup won't help us because the token would be invalid from a server perspective. Instead, we should update what needs to be done and return a count.
2019-07-05 13:50:11 -06:00
J. Ryan Stinnett 60e339bac0 Merge pull request #981 from matrix-org/jryans/reactions-send-marks-unread
Block read marker and read receipt from advancing into pending events
2019-07-05 17:55:52 +01:00
J. Ryan Stinnett ecb88f45b7 Merge pull request #977 from matrix-org/jryans/upgrade-deps
Upgrade dependencies
2019-07-05 17:50:10 +01:00
J. Ryan Stinnett 24a869d15b Update copyright header 2019-07-05 15:07:56 +01:00
J. Ryan Stinnett 1d427a1ea8 Block read marker and read receipt from advancing into pending events
This changes the methods that update the read marker and read receipts to
prevent advancing into pending events.

Part of https://github.com/vector-im/riot-web/issues/9952
2019-07-05 13:59:04 +01:00
J. Ryan Stinnett 90e25867ad Merge pull request #976 from matrix-org/jryans/push-rule-reactions
Add default push rule to ignore reactions
2019-07-05 12:01:31 +01:00
David Baker 2ae56e61cb Merge pull request #979 from matrix-org/dbkr/fix_sync_exception
Fix exception whilst syncing
2019-07-05 11:08:00 +01:00
David Baker 56c0830328 Lint 2019-07-05 10:32:33 +01:00
David Baker 093f139d34 Fix exception whilst syncing
event.getPushRules() may return null (for better or worse...).
Use client.getPushRulesForEvent which will calculate them if they
haven't already been calculated.

Fixes https://github.com/vector-im/riot-web/issues/10269
2019-07-05 10:26:47 +01:00
Travis Ralston 1083efc212 Merge pull request #975 from matrix-org/travis/soft-logout-base
Include the error object when raising Session.logged_out
2019-07-04 09:57:22 -06:00
J. Ryan Stinnett 69773c2619 Upgrade dependencies 2019-07-04 15:04:34 +01:00
J. Ryan Stinnett 2525b5a5d8 Add default push rule to ignore reactions
This adds a default push rule to ignore reactions as proposed in
[MSC2153](https://github.com/matrix-org/matrix-doc/pull/2153). By adding it here
in the client directly, we can try out the idea early even if it hasn't appeared
in the user's HS yet.

Part of https://github.com/vector-im/riot-web/issues/10208
2019-07-04 14:41:04 +01:00
Hubert Chathi b00804102d obsolete todo 2019-07-03 21:37:18 -04:00
Hubert Chathi 8d1d657c44 add unit test for backups signed by cross-signing key 2019-07-03 19:16:26 -04:00
Travis Ralston ff9c84ff94 Fix tests 2019-07-03 16:50:24 -06:00
Travis Ralston 3aa2bf8a76 Include the error object when raising Session.logged_out
Note: The `call` argument previously defined in the SDK was never actually populated, and appears to be a documentation error when the definition was copied from `Call.incoming` directly above it.
2019-07-03 16:42:33 -06:00
Hubert Chathi 6cd09c6af2 pksign was moved to olmlib 2019-07-03 16:00:44 -04:00
Hubert Chathi 46a8486245 rename m.secrets.share to m.secrets.send to agree with latest MSC 2019-07-03 15:15:56 -04:00
Hubert Chathi c5caf8f8f4 sign backups with master key 2019-07-03 15:15:41 -04:00
RiotRobot a229ece693 v2.1.0-rc.1 2019-07-03 16:40:08 +01:00
RiotRobot b435137332 Prepare changelog for v2.1.0-rc.1 2019-07-03 16:40:07 +01:00
Travis Ralston 2cdbc9f4db Merge pull request #974 from matrix-org/travis/e2e-self-notif
Handle self read receipts for fixing e2e notification counts
2019-07-03 09:16:22 -06:00
Michael Telatynski aa6884e484 Merge pull request #973 from matrix-org/t3chguy/show_hidden_redactions_missing_redacts
Add redacts field to event.toJSON
2019-07-03 13:10:16 +01:00
Bruno Windels 4f4d694687 Merge pull request #972 from matrix-org/bwindels/handle-associated-failures
Handle associated event send failures
2019-07-03 09:02:49 +00:00
Travis Ralston 1b47999e80 Handle self read receipts for fixing e2e notification counts
Fixes https://github.com/vector-im/riot-web/issues/9421

This also adds a context to the ReEmitter so we have access to the Room at the time of read receipt. Without this, we have to bind handlers to every encrypted room (which is tedious to maintain) or figure out which room `$something` belong to (CPU intensive).
2019-07-02 13:12:29 -06:00
Michael Telatynski 02427651dd Add redacts field to event.toJSON
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-07-02 19:44:16 +01:00
Bruno Windels 8eeb088e50 allow to clear replacement on redacted events
this is needed when cancelling sending anything related to an event,
to not keep the event as edited when we cancel the edit
before the redaction.
2019-07-02 13:12:13 +02:00
Travis Ralston 3f62581556 Merge pull request #971 from matrix-org/travis/cleanup-debugging
Remove irrelevant debug line from timeline handling
2019-07-01 09:47:36 -06:00
Travis Ralston b53a7f6ee8 Remove irrelevant debug line from timeline handling
See https://github.com/matrix-org/matrix-js-sdk/pull/877#discussion_r271704218
2019-06-28 16:29:18 -06:00
Hubert Chathi 4356603665 save public part of cross-signing keys 2019-06-27 23:37:57 -04:00
Hubert Chathi 1cae5e8b97 fix unit tests to match event name changes 2019-06-27 23:33:07 -04:00
Bruno Windels a4591afba6 clarify when this will return something 2019-06-27 17:53:49 +02:00
Bruno Windels 6ea4c77dd5 expose local echo redaction event on redacted event 2019-06-27 17:53:29 +02:00
Bruno Windels dc3d90d696 keep local redaction event to return accurate status 2019-06-27 17:17:53 +02:00
Bruno Windels 00de919eb4 Merge pull request #969 from matrix-org/bwindels/e2e-edit-history
Handle relations in encrypted rooms
2019-06-27 13:15:59 +00:00
Bruno Windels fdacc2f7ab dont try to decrypt reactions 2019-06-27 15:03:47 +02:00
J. Ryan Stinnett 5a64c29228 Fix logging typo
Regression from https://github.com/matrix-org/matrix-js-sdk/pull/924
2019-06-27 13:27:13 +01:00
Bruno Windels c5cddf1607 take reactions into account when fetching relations from e2e rooms 2019-06-27 14:06:57 +02:00
Bruno Windels 44c7844a4b handle relations in encrypted rooms 2019-06-27 12:35:34 +02:00
Bruno Windels f293da5b34 Merge pull request #967 from matrix-org/bwindels/edit-history
Relations endpoint support
2019-06-26 14:38:46 +00:00
Bruno Windels 4f044de79e add jsdocs 2019-06-26 15:48:39 +02:00
Bruno Windels da116e6077 remove c/p leftover 2019-06-26 15:48:27 +02:00
Bruno Windels 2489180c47 rename pagination token option to from, more in line with cs api 2019-06-26 15:47:59 +02:00
J. Ryan Stinnett 4ec4d330aa Merge pull request #968 from matrix-org/jryans/reactions-disable-encryption
Disable event encryption for reactions
2019-06-26 11:29:47 +01:00
Bruno Windels c75ca1c2d6 fix lint and encode path params properly 2019-06-26 12:10:24 +02:00
Bruno Windels 67462e9fc4 support paginating relations 2019-06-26 12:00:25 +02:00
Bruno Windels 424b6303ef basic support for /relations endpoint 2019-06-26 12:00:25 +02:00
J. Ryan Stinnett 59c4e2c354 Disable event encryption for reactions
For reactions, there is a very little gained by encrypting the entire event, as
relation data is already kept in the clear. Event encryption for a reaction
effectively only obscures the event type, but the purpose is still obvious from
the relation data, so nothing is really gained. It also causes quite a few
problems, such as:

  * triggers notifications via default push rules
  * prevents server-side bundling for reactions

The reaction key / content / emoji value does warrant encrypting, but this will
be handled separately by encrypting just this value.

See https://github.com/matrix-org/matrix-doc/pull/1849#pullrequestreview-248763642
Fixes https://github.com/vector-im/riot-web/issues/10130
2019-06-26 10:59:44 +01:00
Travis Ralston ecca8bc86e Merge pull request #966 from matrix-org/travis/known-safe
Change the known safe room version to version 4
2019-06-25 17:29:24 -06:00
Travis Ralston 43ca920b10 Change the known safe room version to version 4
The default room version in the spec is v4 due to widespread adoption. We should mirror that.
2019-06-25 15:19:28 -06:00
Travis Ralston e1a3f8f053 Merge pull request #965 from matrix-org/travis/detect-ll-r0.5
Check for lazy-loading support in the spec versions instead
2019-06-25 09:02:20 -06:00
Travis Ralston 6e7cb63e7d Check for lazy-loading support in the spec versions instead
Fixes https://github.com/vector-im/riot-web/issues/9966
2019-06-24 13:03:06 -06:00
Travis Ralston ab1177d987 Merge pull request #963 from huan/patch-1
Use cameCase instead of underscore
2019-06-24 11:09:36 -06:00
Huan LI (李卓桓) ee0a1d281d Use cameCase instead of underscore
To make all the variable names to be consistent
2019-06-23 11:56:05 +08:00
Travis Ralston aef7b9a1dc Merge pull request #961 from matrix-org/travis/sas-timeouts
Time out verification attempts after 10 minutes of inactivity
2019-06-20 14:29:18 -06:00
Travis Ralston 7cb8de5b69 Appease the linter 2019-06-20 14:17:51 -06:00
Travis Ralston 5c2fb1c42b Null check and naming 2019-06-20 14:12:16 -06:00
Travis Ralston 553857583d Merge pull request #962 from matrix-org/travis/sas-cancel
Don't handle key verification requests which are immediately cancelled
2019-06-20 09:10:31 -06:00
Travis Ralston 6d0923153f Don't handle key verification requests which are immediately cancelled
Fixes https://github.com/vector-im/riot-web/issues/10083
Fixes https://github.com/vector-im/riot-web/issues/9197
Fixes https://github.com/vector-im/riot-web/issues/8629

The issue is partially fixed by https://github.com/matrix-org/matrix-react-sdk/pull/3123 in that users would no longer see "Incoming request", but would launch their client to a bunch of "key verification cancelled" dialogs. To work around this, we just don't handle key verification requests which we know are cancelled.

The changes are a bit awkward (flagging the event as cancelled instead of filtering it) because:
* We probably don't want to prevent events getting sent over the EventEmitter because applications may still rely on them.
* The cypto side only has visibility of 1 event at a time, so it needs to have some kind of flag to rely on.

An attempt has been made to generalize the new event flag for possible future cases.
2019-06-19 16:46:38 -06:00
Travis Ralston e34eb48914 Don't timeout cancelled requests
The cancelled flag is used upstream and is therefore public.
2019-06-19 14:44:36 -06:00
Travis Ralston 05ab6ef3ab Time out verification attempts after 10 minutes of inactivity
Fixes https://github.com/vector-im/riot-web/issues/10117
2019-06-19 14:15:58 -06:00
RiotRobot 81eefc1377 v2.0.1 2019-06-19 15:43:38 +01:00
RiotRobot 895d854e1c Prepare changelog for v2.0.1 2019-06-19 15:43:38 +01:00
RiotRobot e432d4f808 v2.0.1-rc.2 2019-06-18 15:28:47 +01:00
RiotRobot 56c0ae294b Prepare changelog for v2.0.1-rc.2 2019-06-18 15:28:47 +01:00
David Baker 0b9d68b4f2 Merge remote-tracking branch 'origin/develop' into release-v2.0.1 2019-06-18 15:24:51 +01:00
Bruno Windels e2e034f795 Merge pull request #960 from matrix-org/bwindels/redactions-blended-echo
return 'sending' status for an event that is only locally redacted
2019-06-18 13:09:03 +00:00
Bruno Windels bb5e3d51b8 remove redundant localecho part from method name 2019-06-18 14:58:17 +02:00
Bruno Windels 70b23614b5 comment typo 2019-06-18 14:55:58 +02:00
Bruno Windels 24a75e3765 return 'sending' status for an event that is only locally redacted 2019-06-18 13:46:34 +02:00
Hubert Chathi 07c2e34d87 Merge branch 'develop' into dbkr/cross_signing 2019-06-14 22:57:02 -04:00
Hubert Chathi 5bcbe76f2c cleanups and a lot more docs 2019-06-14 22:50:29 -04:00
Hubert Chathi d694ee3ef3 Merge pull request #954 from uhoreg/fix_verification_request
Key verification request fixes
2019-06-14 15:39:01 -04:00
David Baker efbdf4e1a8 Merge pull request #956 from matrix-org/dbkr/resurrect_riot_bot
Add flag to force saving sync store
2019-06-14 17:20:27 +01:00
David Baker 44bfc2e846 Add flag to force saving sync store
Add a 'force' flag to to the save method of the store to force the
store to sace its data even if it wouldn't normally.
2019-06-14 15:57:49 +01:00
Travis Ralston 0121bdbb75 welcome back, Olm 2019-06-14 08:23:27 -06:00
David Baker b8ba77a7b5 Merge pull request #953 from matrix-org/dbkr/simplify_email_reg
Expose the inhibit_login flag to register
2019-06-13 18:22:23 +01:00
David Baker 65dd5cc6ad use right variable 2019-06-13 16:30:39 +01:00
David Baker 8aeb994839 Expose the inhibit_login flag to register 2019-06-13 16:23:11 +01:00
Hubert Chathi 64daa444dd Key verification request fixes
- fix requestVerification in MatrixClient to match the function in crypto
  - reorder the arguments so that the arguments actually do what they say they
    do
  - pass through the third argument, which was accidentally omitted
- ignore verification requests from ourselves
- also fix a comment
2019-06-13 10:55:06 -04:00
Bruno Windels 26aab4f38d Merge pull request #947 from matrix-org/bwindels/relations-unsent
Support redactions and relations of/with unsent events.
2019-06-13 11:32:37 +00:00
Bruno Windels 6059df1b67 move CANCELLED check deeper into aggregation path 2019-06-13 12:28:02 +02:00
Bruno Windels 2a0c85c772 add hasAssociation helper 2019-06-13 12:28:02 +02:00
Bruno Windels 3488fbe64c expand comment why need to preserve redaction local echo on remote echo 2019-06-13 12:28:02 +02:00
Bruno Windels 811a98ad19 whitespace, newlines 2019-06-13 12:28:02 +02:00
Bruno Windels 4462f4b90e add isRedaction helper on Event 2019-06-13 12:28:02 +02:00
Bruno Windels 4143a79f7b rename related id to associated id 2019-06-13 12:26:38 +02:00
Bruno Windels 3ed9b00398 clarify why we need to listen for remote echo of related event 2019-06-13 12:26:38 +02:00
Bruno Windels b005b75331 comment typo
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-06-13 12:26:38 +02:00
Bruno Windels a9f9e2cf35 comment typo
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-06-13 12:26:38 +02:00
Bruno Windels 5602b94dcb make sure where not re-adding cancelled events when undoing local red. 2019-06-13 12:26:38 +02:00
Bruno Windels 930de640ac don't add events from /sync that have been locally redacted
it'll cause the reactions counter to go up and down while reactions
and redactions come in.

In case the local redaction gets cancelled,
Room._revertRedactionLocalEcho will add the relation back to
the relations collection.
2019-06-13 12:26:38 +02:00
Bruno Windels 6d9fba8191 preserve (locally) redacted state after applying remote echo
because the RedactionDimensions was trying to redact an event
that was already redacted after it's remote echo had come in
but it's redaction hadn't synced yet.
2019-06-13 12:26:38 +02:00
Bruno Windels 624c6f0a6e get the txnId from the correct place to delete event after remote echo 2019-06-13 12:26:38 +02:00
Bruno Windels 7d2f7fae45 fix tests 2019-06-13 12:26:38 +02:00
Bruno Windels 3f917b39c9 fix lint 2019-06-13 12:26:38 +02:00
Bruno Windels f1336a5ce7 rename target id to related id and add jsdoc comments 2019-06-13 12:26:38 +02:00
Bruno Windels 7a10d504b2 emit Relations.redaction synchronously, timeout should not be needed
listeners shouldn't care about the original event, as it's removed
from the Relations collection already.
2019-06-13 12:26:38 +02:00
Bruno Windels 831aec6488 emit remote id once received so enqueued relations have it when sent 2019-06-13 12:26:38 +02:00
Bruno Windels 6eb229ac1e first look in pending event list for event being redacted
in case the redacted event hasn't been sent yet
2019-06-13 12:26:38 +02:00
Bruno Windels c58db665dd give the client a chance to run room.updatePendingEvent after sending
before the next event is sent. This is needed to update the target id
if it was the local id of the event that was just sent.
2019-06-13 12:26:38 +02:00
Bruno Windels e222fb1783 enqueue relations and redactions as well
as they might need to wait until their target has been sent
2019-06-13 12:26:38 +02:00
Hubert Chathi 4c6fa89053 various cross-signing fixes and improvements 2019-06-12 11:47:12 -04:00
Hubert Chathi 98815ffdf6 allow http request stub to ignore unhandled syncs 2019-06-12 11:41:26 -04:00
RiotRobot 31a0192c2d v2.0.1-rc.1 2019-06-12 11:47:36 +01:00
RiotRobot 53f8091e3a Prepare changelog for v2.0.1-rc.1 2019-06-12 11:47:35 +01:00
David Baker 012cbf7995 Merge pull request #952 from matrix-org/jryans/file-api-changes
Fix content uploads for modern browsers
2019-06-11 13:11:53 +01:00
J. Ryan Stinnett ac26c91cba Fix content uploads for modern browsers
Modern browsers now expose a `stream` function on the Blob and File interfaces.
This conflicts with an older style of passing data to the `uploadContent` SDK
method, which supported supplying the data to upload in the `stream` property of
an object.

Since this old style is still in active use in the Matrix JS ecosystem, we
preserve the backwards compatibility for now by checking whether `stream` is a
function.

This fix has been tested in Firefox Nightly (69), Firefox Release (67), Chrome
Canary (77), and Chrome Stable (75).

Fixes https://github.com/vector-im/riot-web/issues/9913
Fixes https://github.com/matrix-org/matrix-js-sdk/issues/949
2019-06-11 13:02:42 +01:00
David Baker c13162aada Merge pull request #951 from matrix-org/dbkr/one_request_at_a_time_two
Don't overlap auth submissions with polls
2019-06-11 10:42:32 +01:00
David Baker 9fb6eea8b7 Document what to use instead 2019-06-10 18:04:30 +01:00
David Baker 23c4f19cda lint 2019-06-10 16:29:55 +01:00
David Baker 3b34570749 doc background flag deprecation 2019-06-10 16:26:09 +01:00
David Baker 0412ca5810 make busyChanged optional 2019-06-10 16:24:20 +01:00
David Baker c80518bf3e s/setBusy/busyChanged/ 2019-06-10 16:23:06 +01:00
David Baker 61ee6eb8af This should be null, not false
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-06-10 16:19:45 +01:00
David Baker 654e8b41fa Don't overlap auth submissions with polls
Wait for polls to complete before submitting auth dicts, otherwise
we risk the requests overlapping and both returning a 200.

Also introduces a setBusy interface to interactive-auth to explicitly
set the busy status, since otherwise it doesn't get set until the
request actually starts.
2019-06-10 15:18:46 +01:00
J. Ryan Stinnett 7080458f7e Merge pull request #945 from matrix-org/jryans/funding
Add funding details for GitHub sponsor button
2019-06-07 20:42:28 +01:00
J. Ryan Stinnett 08d236f5ec Add funding details for GitHub sponsor button 2019-06-07 11:46:56 +01:00
David Baker e332a7d113 Merge pull request #944 from matrix-org/dbkr/verify_signature_modifies_the_object_because_everything_is_awful
Fix backup sig validation with multiple sigs
2019-06-07 11:26:13 +01:00
David Baker 7879709f62 Fix backup sig validation with multiple sigs
verifySignature modifies the object so we need to clone if we're
verifying more than one signature.

Fixes https://github.com/vector-im/riot-web/issues/9357
2019-06-07 11:05:44 +01:00
David Baker 56e030762e Merge pull request #943 from matrix-org/dbkr/wait_for_token_request
Don't send another token request while one's in flight
2019-06-06 19:18:20 +01:00
David Baker bac73150ca Don't send another token request while one's in flight
Otherwise we end up with more tokens than are strictly necessary
2019-06-06 19:03:29 +01:00
David Baker 2e1fb15ada Merge pull request #942 from matrix-org/dbkr/one_poll_at_a_time
Don't poll UI auth again until current poll finishes
2019-06-06 18:36:34 +01:00
David Baker ae9bcd6f6c Don't poll UI auth again until current poll finishes
On slow networks/servers we were ending up with lots of requests in
flight.
2019-06-06 18:31:54 +01:00
Travis Ralston c18c679b9b Merge pull request #938 from matrix-org/travis/fail-fast-but-not-too-fast
Provide the discovered URLs when a liveliness error occurs
2019-06-06 09:03:39 -06:00
Travis Ralston d014ee0b72 Merge pull request #941 from matrix-org/travis/redact-v3
Encode event IDs when redacting events
2019-06-06 08:29:15 -06:00
Travis Ralston a30ef7250b Encode event IDs when redacting events
Because v3 rooms are a thing.
2019-06-05 15:27:55 -06:00
Hubert Chathi 6f6e7ea921 verify cross-signing key with SAS 2019-06-05 15:27:31 -04:00
Hubert Chathi 0c714ba4a1 some cleanups 2019-06-05 15:24:03 -04:00
Hubert Chathi 5f539aacd9 Merge branch 'develop' into dbkr/cross_signing 2019-06-05 15:21:17 -04:00
Hubert Chathi 570ce4f4b7 Merge pull request #940 from uhoreg/fix_missing_logger
add missing logger
2019-06-05 14:49:38 -04:00
Hubert Chathi 3c7c9048eb add missing logger 2019-06-05 14:04:39 -04:00
Hubert Chathi 6a77df7b41 Merge branch 'develop' into dbkr/cross_signing 2019-06-05 12:48:17 -04:00
Hubert Chathi 41243757ee Merge pull request #939 from uhoreg/unpicky_verification
verification: don't error if we don't know about some keys
2019-06-05 12:18:53 -04:00
Hubert Chathi 2af311bd7d verification: don't error if we don't know about some keys 2019-06-05 11:56:37 -04:00
Bruno Windels 1bc9ee7110 Merge pull request #937 from matrix-org/bwindels/redactions-local-echo
Local echo for redactions
2019-06-05 07:49:31 +00:00
Bruno Windels 4e040f8e77 correct comments about redaction events 2019-06-05 09:41:52 +02:00
Travis Ralston 26c1c6db3b Fix tests and populate the right IS validation object 2019-06-04 23:51:41 -06:00
Travis Ralston d38da83656 Provide the discovered URLs when a liveliness error occurs
See https://github.com/vector-im/riot-web/issues/9828
2019-06-04 23:39:31 -06:00
Hubert Chathi 4a9a1b40e9 initial implementation of secret storage and sharing 2019-06-04 15:04:45 -04:00
Hubert Chathi dc971b9a59 add missing semicolon 2019-06-04 14:58:46 -04:00
Bruno Windels 58f163ed5c emit Room.redactionCancelled event when undoing redaction local echo 2019-06-04 18:45:13 +02:00
Bruno Windels c0c9f0122c remove leftover newline 2019-06-04 16:08:14 +02:00
Bruno Windels d33395e46d improve naming and commenting for _aggregateNonLiveRelation 2019-06-04 15:54:31 +02:00
Bruno Windels b83c7d3929 unneeded check, as redacted_because is now also set for local echo 2019-06-04 15:49:52 +02:00
Bruno Windels b5df016b1b remove unused method 2019-06-04 15:28:40 +02:00
Bruno Windels a8b6be3b38 also set redacted_because with redaction local echo 2019-06-04 13:37:24 +02:00
Bruno Windels 78cf175f5a also look for redaction local echo event in pendingList
also re-aggregate the relation if it's redaction has been cancelled
2019-06-04 11:55:48 +02:00
Travis Ralston 1b78856a7d Merge pull request #934 from matrix-org/travis/re-check-version
Refresh safe room versions when the server looks more modern than us
2019-06-04 01:40:57 -06:00
Bruno Windels 8194287391 make redactEvent go through same local-echo aware path as other events 2019-06-03 18:37:25 +02:00
Bruno Windels 2eecea9a07 handle redactions in room pending event logic 2019-06-03 18:37:01 +02:00
Bruno Windels 465032dd4f support marking an event as redacted in a way we can undo it later
in case the redaction can't be sent
2019-06-03 18:36:03 +02:00
Travis Ralston e473315a89 Check for the correct capability when refreshing 2019-06-03 09:56:55 -06:00
Travis Ralston 9d34ad5287 Merge pull request #935 from matrix-org/travis/v4-rooms
Add v4 as a safe room version
2019-06-03 07:11:13 -06:00
Travis Ralston a532cc5cf9 Add v4 as a safe room version
It's listed as stable in the spec, and this is for our fallback.
2019-06-02 23:36:02 -06:00
Travis Ralston 60c6c5bc41 Refresh safe room versions when the server looks more modern than us
Fixes https://github.com/vector-im/riot-web/issues/9845
2019-06-02 23:34:58 -06:00
J. Ryan Stinnett 0cbbbe8503 Merge pull request #933 from matrix-org/jryans/disable-guard-for-in
Disable guard-for-in rule
2019-05-31 18:10:57 +01:00
J. Ryan Stinnett 47a8d3e50a Disable guard-for-in rule
The Google code style config for ESLint turns on `guard-for-in` to require
for-in loops to check own properties. This makes it annoying to iterate objects,
and we seem to disable the rule by line comments when it comes up anyway, so
this just disables it globally.
2019-05-31 16:36:54 +01:00
RiotRobot 304da09f3b Merge branch 'master' into develop 2019-05-31 10:30:58 +01:00
RiotRobot acd4dcb56e v2.0.0 2019-05-31 10:29:21 +01:00
RiotRobot c170456cde Prepare changelog for v2.0.0 2019-05-31 10:29:20 +01:00
J. Ryan Stinnett 4e739e4b06 Merge pull request #932 from matrix-org/jryans/login-change-release
Saves access_token and user_id after login for all login types
2019-05-31 10:05:34 +01:00
J. Ryan Stinnett 137c6919f6 Fix undefined logger in webstorage.js 2019-05-31 10:01:32 +01:00
J. Ryan Stinnett 842ce30190 Fix lint error in login.spec.js 2019-05-31 09:54:47 +01:00
J. Ryan Stinnett df1539040c Fix lint error in login.spec.js 2019-05-31 09:54:11 +01:00
Sergii Stotskyi 2a04459bb2 fix(login): saves access_token and user_id after login for all login types
Signed-off-by: Sergii Stotskyi <sergiy.stotskiy@gmail.com>

Fixes #876
2019-05-31 09:40:51 +01:00
J. Ryan Stinnett 6367bf7c75 Merge pull request #931 from matrix-org/jryans/key-backup-base-x
Fix recovery key encoding for base-x 3.0.5
2019-05-30 18:29:17 +01:00
Travis Ralston a0456dc430 Merge pull request #924 from jkasun/loglevel-extend
Extend loglevel logging for the whole project
2019-05-30 11:20:49 -06:00
Travis Ralston 52ec831b16 Merge pull request #930 from stalniy/fix/save-login-state
fix(login): saves access_token and user_id after login for all login types
2019-05-30 11:20:01 -06:00
J. Ryan Stinnett 8263062fab Fix recovery key encoding for base-x 3.0.5
This fixes our recovery key encoding to work with base-x 3.0.5, which requires the
encoding input to be in a `Buffer`. (base-x is a dependency of bs58.)

Unfortunately, base-x hasn't marked this as breaking change through a major
version increment, so semantic versioning can't help us here to ensure we get
particular version.

I have verified that the change to `Buffer` works with both old and new base-x,
so we should be okay no matter what version is actually selected.

For extra fun, base-x 3.0.5 also uses newer JS features, which `uglify-js`
doesn't support. I have migrated to `terser` instead, which is what we're using
for Riot these days (via Webpack).

Fixes https://github.com/vector-im/riot-web/issues/9901
2019-05-30 15:42:39 +01:00
janith 9a2bf78a8e logger.dir changed to a log 2019-05-30 12:58:32 +05:30
janith cb16f7a60b Minor Fixes 2019-05-30 09:35:37 +05:30
janith ad84631ddb Change logger import to ES6 2019-05-30 09:27:25 +05:30
Hubert Chathi 95131c7658 add test for syncing trust on another user 2019-05-29 17:01:25 -04:00
Hubert Chathi 936eef194a minor fixes to tests 2019-05-29 17:01:13 -04:00
Hubert Chathi 941d871daf fix check for empty cross-signing repsonse 2019-05-29 16:59:51 -04:00
Hubert Chathi 609ee663fa use the right path for logger 2019-05-29 16:58:49 -04:00
RiotRobot d78426d708 Merge branch 'master' into develop 2019-05-29 15:53:51 +01:00
RiotRobot a9543df6db v1.2.0 2019-05-29 15:52:20 +01:00
RiotRobot aae388be93 Prepare changelog for v1.2.0 2019-05-29 15:52:19 +01:00
Hubert Chathi 53804cac5c save cross-signing keys from sync and verify new keys for user 2019-05-28 22:28:54 -04:00
Sergii Stotskyi 4ef970b4da fix(login): saves access_token and user_id after login for all login types
Signed-off-by: Sergii Stotskyi <sergiy.stotskiy@gmail.com>

Fixes #876
2019-05-28 17:47:49 +03:00
Travis Ralston b199f133b3 fixup readme 2019-05-27 10:41:04 -06:00
Travis Ralston 7d1b183a1b Merge pull request #929 from spantaleev/fix-non-integer-media-sizes
Do not try to request thumbnails with non-integer sizes
2019-05-27 08:37:03 -06:00
Slavi Pantaleev 49d119e92e Do not try to request thumbnails with non-integer sizes
Issue described in: https://github.com/vector-im/riot-web/issues/9690

matrix-react-sdk was patched separately, so that it won't call `mxcUrlToHttp()`
(and in turn `getHttpUriForMxc()`) with non-integer sizes.

This patch fixes the JS SDK as well, hoping to prevent the same issue
from happening on other clients (besides matrix-react-sdk / riot-web).

Signed-off-by: Slavi Pantaleev <slavi@devture.com>
2019-05-27 11:25:35 +03:00
Janith Kasun feed1da570 Merge branch 'develop' into loglevel-extend 2019-05-24 18:00:40 +05:30
Hubert Chathi 193ad9e09d use 3 keys for cross-signing 2019-05-23 18:18:21 -04:00
Travis Ralston a3ad835d84 Merge pull request #928 from matrix-org/revert-927-travis/wk-discovery
Revert "Add a bunch of debugging to .well-known IS validation"
2019-05-23 14:47:48 -06:00
Travis Ralston f8afee8ebd Revert "Add a bunch of debugging to .well-known IS validation" 2019-05-23 14:47:38 -06:00
Travis Ralston 7e955fc312 Merge pull request #927 from matrix-org/travis/wk-discovery
Add a bunch of debugging to .well-known IS validation
2019-05-23 14:19:33 -06:00
Travis Ralston eebf92366f Add a bunch of debugging to .well-known IS validation 2019-05-23 14:00:17 -06:00
RiotRobot 3c23e166a7 Upgrade jsdoc 2019-05-23 16:30:30 +01:00
RiotRobot 26a8439ce4 Upgrade jsdoc 2019-05-23 16:28:26 +01:00
RiotRobot 2c2e8fa1ac v1.2.0-rc.1 2019-05-23 16:11:35 +01:00
RiotRobot ddc2fa74b9 Prepare changelog for v1.2.0-rc.1 2019-05-23 16:11:35 +01:00
David Baker 93d51b83c3 Merge pull request #926 from matrix-org/dbkr/uiauth_send_email
interactive-auth now handles requesting email tokens
2019-05-22 14:24:08 +01:00
David Baker f83eae4a46 typing hard 2019-05-22 13:11:05 +01:00
David Baker 87c6d11fca PR feedback 2019-05-22 13:09:24 +01:00
David Baker e87ac86e48 interactive-auth now handles requesting email tokens
interactive-auth now has a callback to request the email token which
it will call at the appropriate time (now at the start of the
auth process if the chosen flow contain an email auth stage).

This does make this a breaking change, although not sure this is
used outside of Riot. We could make it backwards compatible by
having an option for the new behaviour. It may not be worthwhile
though.

https://github.com/vector-im/riot-web/issues/9586
2019-05-22 11:51:05 +01:00
Janith Kasun de8063a43a Merge branch 'develop' into loglevel-extend 2019-05-19 09:40:38 +05:30
jkasun a73dabcb67 Console logging to loglevel 2019-05-19 09:29:40 +05:30
Bruno Windels 7782e81101 Merge pull request #923 from matrix-org/bwindels/caneditcheckfix
allow access to unreplaced message content
2019-05-17 12:32:45 +00:00
Bruno Windels aa70687d9e allow access to unreplaced message content 2019-05-17 13:25:21 +01:00
Bruno Windels 38d32de06b Merge pull request #922 from matrix-org/bwindels/editedmarker
Add method to retrieve replacing event
2019-05-17 11:04:00 +00:00
Bruno Windels 7720c72b73 correct return type 2019-05-17 12:03:36 +01:00
Bruno Windels ff9505073f add method to retrieve replacing event 2019-05-17 11:56:28 +01:00
David Baker 21ee1c31a7 Merge pull request #921 from matrix-org/dbkr/really_log_useful_info_on_verify_failure
More logging when signature verification fails
2019-05-17 10:18:34 +01:00
David Baker fd01ba1fcf More logging when signature verification fails
Still no luck diagnosing https://github.com/vector-im/riot-web/issues/9357
so adding more logging.
2019-05-17 10:08:27 +01:00
Bruno Windels fbf53524ed Merge pull request #920 from matrix-org/bwindels/message-editing-local-echo
Local echo for m.replace relations
2019-05-16 15:22:12 +00:00
Bruno Windels 1f2a701ace remove double newline 2019-05-16 16:14:53 +01:00
Bruno Windels 0b87a573b3 reduce indenting by returning early, also actually pass replacement!
also add comment
2019-05-16 16:14:19 +01:00
Bruno Windels 74438716af PR feedback/cleanup 2019-05-16 15:54:15 +01:00
Bruno Windels d10b348e74 clear event replacement on redaction
as redaction supersedes a replacement
2019-05-16 15:53:46 +01:00
Bruno Windels 68e9be47d9 check if an incoming event is the target of a Relations 2019-05-16 15:49:55 +01:00
Bruno Windels bddd03c2fd separate setTargetEvent method
call makeReplaced from addEvent instead so it's all done from Relations
2019-05-16 15:48:03 +01:00
Bruno Windels e23ba50dd8 only call aggregation code for local echo for relations 2019-05-16 14:42:09 +01:00
Bruno Windels 261ab7ae68 don't block local echo for m.replace anymore! 2019-05-16 14:40:55 +01:00
Bruno Windels 21c8c76dc3 helper method to get sending status of event or replacement 2019-05-16 14:40:39 +01:00
Bruno Windels 266d0f9d05 remove flag for edits as it's not handled separately anymore 2019-05-16 14:40:17 +01:00
Bruno Windels 69d25c1498 emit from MatrixEvent.makeReplaced instead of Room
now that event can be replaced from Relations instead of Room

Also make `makeReplaced` non-destructive by not touching the original
event.content, so it can be undone by later calls.
2019-05-16 14:38:48 +01:00
Bruno Windels 3cd2b3925a re-evaluate last replacement on redaction and local echo cancellation 2019-05-16 14:36:38 +01:00
Bruno Windels 33e9eb371e use relation handling in timelineset for replacements 2019-05-16 14:35:50 +01:00
Bruno Windels 07572d1e8d helper method to get last valid replacement 2019-05-16 14:34:08 +01:00
Bruno Windels dde4f558f3 set targetEvent on Relations, once known 2019-05-16 14:33:24 +01:00
J. Ryan Stinnett 79d2574ea7 Merge pull request #919 from matrix-org/jryans/reactions-status-bar
Track relations as pending and remove when cancelled
2019-05-16 13:39:41 +01:00
J. Ryan Stinnett 51fb5c4a15 Update aggregation by sender on remove
This updates the aggregation by sender in the relations collection on removal.
It also changes this aggregation to use a Set, so consumers will need to update.
2019-05-16 12:40:19 +01:00
J. Ryan Stinnett 875c6b973b Remove cancelled relations from the relations collection
This listens for event status changes on sending events in case they might be
cancelled and removes them from aggregation if so.

Part of https://github.com/vector-im/riot-web/issues/9731
2019-05-16 12:29:28 +01:00
J. Ryan Stinnett 21e1312dd7 Allow relations into the pending event list
Relations actually should go into the pending event list, just like messages.
This is the easiest way to keep them in a holding area in case of unverified
devices, etc.

We still want the relations to local echo immediately, so we directly trigger
the aggregation on the timeline sets.
2019-05-16 12:26:36 +01:00
J. Ryan Stinnett a722ef3b03 Merge pull request #916 from matrix-org/jryans/stringify-events
Add stringify helper to summarise events when debugging
2019-05-16 09:44:39 +01:00
J. Ryan Stinnett 80ba5d29f2 Add stringify helper to summarise events when debugging 2019-05-16 09:31:19 +01:00
Bruno Windels a35e6a0f54 Merge pull request #918 from matrix-org/bwindels/message-edit-editor3
Message editing: filter out replacements for senders that are not the original sender
2019-05-15 17:21:03 +00:00
J. Ryan Stinnett bdc1958c08 Merge pull request #917 from matrix-org/jryans/encrypted-reactions
Wait until decrypt before aggregating
2019-05-15 17:26:07 +01:00
Bruno Windels 3f2bac71c6 filter out replacements for senders that are not the original sender 2019-05-15 15:52:37 +01:00
J. Ryan Stinnett 6b9a11b697 Wait until decrypt before aggregating
For encrypted annotations, we need to wait until the event has been decrypted
before adding it to the relations collection.
2019-05-15 15:48:33 +01:00
J. Ryan Stinnett f17ecba519 Add getRelation helper
This adds a `getRelation` helper to ensure we always read relation info from the
wire content as required in E2E rooms.
2019-05-15 15:48:33 +01:00
Bruno Windels ce0b014a5a Merge pull request #914 from matrix-org/bwindels/message-edit-editor2
Message editing: mark original event as replaced instead of replacing the event object
2019-05-15 14:11:38 +00:00
Bruno Windels ad48d2997e prevent earlier replacements from messing things up 2019-05-15 14:54:52 +01:00
Bruno Windels 1c1781ce76 make replacements work in e2e rooms 2019-05-15 14:54:21 +01:00
Bruno Windels 5fd001354a replace content when replacing instead of evaluating in getContent 2019-05-15 14:53:44 +01:00
Bruno Windels a18bdad44f dont replace a redacted event 2019-05-15 13:56:13 +01:00
Bruno Windels 600dff62e8 detect relations on encrypted events properly 2019-05-15 13:56:00 +01:00
Bruno Windels db7a402e9b mark original event as replaced instead of replacing the event object
this is more in line with what happens on the server-side,
and also doesn't break existing reply relations.
2019-05-15 12:05:39 +01:00
Bruno Windels 0e53f9052f Merge pull request #913 from matrix-org/bwindels/message-edit-editor
Support for replacing message through m.replace relationship.
2019-05-15 09:22:34 +00:00
Bruno Windels 62e69cacb7 remove leftover code, fix lint 2019-05-14 15:52:02 +01:00
Bruno Windels 852c88c341 add unstableClientRelationReplacements in js-sdk 2019-05-14 15:33:49 +01:00
Bruno Windels 455f52f1f5 remove logging 2019-05-14 15:25:47 +01:00
Bruno Windels df6012c58d completely avoid local echo for edits for now 2019-05-14 15:25:07 +01:00
Bruno Windels f68a3dde46 cleanup 2019-05-14 15:24:57 +01:00
Bruno Windels 25e6b1cac8 handle m.replace relations in room, emit Room.replaceEvent 2019-05-14 15:24:36 +01:00
Travis Ralston 4ad20526db Merge pull request #912 from matrix-org/travis/wk/timeouts
Use a short timeout for .well-known requests
2019-05-14 08:24:09 -06:00
Bruno Windels 18cd017f58 support marking an as replacing another
and take if the timestamp of the original event if so
also helper methods
2019-05-14 15:23:22 +01:00
Bruno Windels 0161664b6c add support for replacing an existing event in a timeline(set) 2019-05-14 15:22:46 +01:00
Travis Ralston 25df31bf96 Use a short timeout for .well-known requests
Applies to verification of the homeserver, identity server, and fetching of the .well-known objects. Does not affect other HTTP requests.

See https://github.com/vector-im/riot-web/issues/9290
2019-05-13 18:39:59 -06:00
Travis Ralston 09438b440e Fix spelling error in txnId for redactions
Fixes https://github.com/vector-im/riot-web/issues/9700
2019-05-13 13:43:44 -06:00
J. Ryan Stinnett 6a5f5b249e Merge pull request #911 from matrix-org/jryans/aggregations
Redaction and change events for relations
2019-05-13 15:20:15 +01:00
J. Ryan Stinnett 3a20114c39 Change to event-level beforeRedaction event for efficiency
To avoid an O(n^2) situation with every relations container trying to process
every redaction that may occur in a room, this switches to an event-level
notification, so that the specific relations container who cares can listen to
just the events it wants to know about.
2019-05-13 14:35:39 +01:00
J. Ryan Stinnett f411d50253 Clarify before redaction event timing 2019-05-13 14:17:23 +01:00
J. Ryan Stinnett 00851df25c Always add pending relation events to the timeline sets directly
This special cases pending relation events to go directly to the timeline sets
and ignores the `pendingEventOrdering` option. This feels a bit strange in the
code, so we should revisit this choice when we stabilized relation support.
2019-05-13 13:52:37 +01:00
J. Ryan Stinnett 8822d255b3 Fix indentation in src/models/event.js 2019-05-13 13:52:37 +01:00
J. Ryan Stinnett 4b4ba86167 Add Relations.add event for additional relations in collection
This adds a `Relations.add` event that consumers can listen for to be notified
each time an additional relation event is added to a relations collection.

Part of https://github.com/vector-im/riot-web/issues/9485
Part of https://github.com/vector-im/riot-web/issues/9572
2019-05-13 13:52:37 +01:00
J. Ryan Stinnett 7ea820f6e1 Add Event.relationsCreated event to listen for future relations collections
If you ask for relations but none currently exist, we return `null` to avoid the
overhead of many empty relations objects in memory. However, we still want some
way to alert consumers when one _is_ later made, so this event level event
provides that.

Part of https://github.com/vector-im/riot-web/issues/9572
Part of https://github.com/vector-im/riot-web/issues/9485
2019-05-13 13:52:37 +01:00
J. Ryan Stinnett 53d8cf0852 Update relation collections after redaction
This watches for redactions of relations and updates the relations collection
to match, including various aggregations. In addition, a redaction event is
emitted on the redaction collection to notify consumers of the change.

Part of https://github.com/vector-im/riot-web/issues/9574
Part of https://github.com/vector-im/riot-web/issues/9485
2019-05-13 13:52:37 +01:00
J. Ryan Stinnett 761806c678 Add support for class properties
This enables compiler and linting features to allow class properties like we do
in the React SDK.
2019-05-13 13:52:37 +01:00
J. Ryan Stinnett d6abd639f3 Merge pull request #910 from matrix-org/jryans/aggregations
Add basic read path for relations
2019-05-09 12:01:57 +01:00
J. Ryan Stinnett 6078bbbe24 Add basic read path for relations
This adds a read path for relations (gated behind an unstable option). A few
basic client-side grouping and sorting operations are supported. Consumers are
expected to ask the `EventTimelineSet` for a relation container when desired.
2019-05-08 18:05:52 +01:00
J. Ryan Stinnett c1c81df4de Intern rel_type for relations
In anticipation of relations being quite frequently used, we should intern
strings of common fields, such as `rel_type`.
2019-05-08 17:41:06 +01:00
David Baker ee8a4698a9 Merge branch 'master' into develop 2019-05-07 15:25:23 +01:00
David Baker c1956d3f05 v1.1.0 2019-05-07 15:23:32 +01:00
David Baker 56316dc5d9 Prepare changelog for v1.1.0 2019-05-07 15:23:32 +01:00
Hubert Chathi 405451d783 complete some more unit tests 2019-05-03 23:23:08 -04:00
Hubert Chathi b0275afac2 remove some debugging lines 2019-05-03 23:22:51 -04:00
Hubert Chathi ae71f41138 add missing files 2019-05-03 18:12:17 -04:00
Hubert Chathi ec2f07e1aa add methods for signing and checking users and devices with cross-signing 2019-05-03 18:05:36 -04:00
Travis Ralston b5c74b5666 Merge pull request #860 from matrix-org/travis/tombstone-notif
Add a concept of default push rules, using it for tombstone notifications
2019-05-03 11:21:37 -06:00
Travis Ralston dc946dffbc Add some words to explain why we do things the way we do 2019-05-03 11:19:46 -06:00
David Baker 937baadb9b Merge pull request #907 from matrix-org/dbkr/yarn_upgrade_may19
yarn upgrade
2019-05-03 12:00:03 +01:00
David Baker 8d0c03b4f0 Add customer resolution for base-x
to fix dependency version to 3.0.4 (ie. the version that exports
ES5 rather than ES6)
2019-05-03 11:46:11 +01:00
David Baker a3fba73044 Set base-x back to 3.0.4
3.0.5 exports ES6 which breaks the build.

Also specifically depending on version 3.0.4 in the package.json
doesn't look like it has the desired effect now (yarn just installs
two separate versions) so remove that.
2019-05-03 11:03:29 +01:00
David Baker 116cf31199 yarn upgrade 2019-05-03 10:50:23 +01:00
J. Ryan Stinnett cdb78e4c75 Remove noisy debug logs
The debug logs in the sync loop haven't been helpful so far, and they are quite
noisy pushing other logs out of the way, so this change removes them.
2019-04-30 15:43:54 +01:00
David Baker 0bb9c56e94 Merge branch 'develop' into release-v1.1.0 2019-04-30 11:50:24 +01:00
David Baker 821f1c876b Undo unintentional commenting 2019-04-30 11:48:38 +01:00
David Baker e9b95f8567 v1.1.0-rc.1 2019-04-30 11:43:35 +01:00
David Baker 103d811441 Prepare changelog for v1.1.0-rc.1 2019-04-30 11:43:34 +01:00
David Baker bb4f5a3fa1 Get the name of the pipeline right 2019-04-26 18:07:04 +01:00
David Baker f5cbdeac8f Trigger react-sdk build in buildkite pipeline 2019-04-26 18:06:04 +01:00
Hubert Chathi 56062e8e4e Merge pull request #903 from uhoreg/olm-3.1.0
use the release version of olm 3.1.0
2019-04-24 18:19:15 -04:00
Hubert Chathi 03c85d48e5 update the yarn.lock with the olm release 2019-04-24 12:07:10 -04:00
Hubert Chathi dd8f0fbdcb use the release version of olm 3.1.0 2019-04-23 18:06:09 -04:00
Travis Ralston fac61a76e9 Merge pull request #901 from matrix-org/travis/olm2
Use new Olm repo link in README
2019-04-18 08:26:12 -06:00
Travis Ralston 3b09ab3ca1 Use new Olm repo link in README 2019-04-17 23:50:51 -06:00
Travis Ralston 16bfe79305 Merge pull request #897 from matrix-org/travis/wk-custom
Support being fed a .well-known config object for validation
2019-04-17 09:58:22 -06:00
J. Ryan Stinnett d668f97c98 Clarify comment
Co-Authored-By: turt2live <travpc@gmail.com>
2019-04-17 09:44:52 -06:00
Bruno Windels 18bd10b03d Merge pull request #900 from matrix-org/bwindels/stylepreviewbar
emit self-membership event at end of handling sync update
2019-04-17 12:31:16 +00:00
Bruno Windels 6b1d089caf Merge branch 'develop' into bwindels/stylepreviewbar 2019-04-17 14:24:25 +02:00
Travis Ralston af93401385 Use more appropriate errors for some situations 2019-04-16 11:13:36 -06:00
Travis Ralston fa5add3d99 Use the right error codes 2019-04-16 11:03:58 -06:00
Travis Ralston fb971580e0 Merge branch 'develop' into travis/wk-custom 2019-04-16 11:01:49 -06:00
Bruno Windels dcaea98e33 emit self-membership event at end of handling sync update
otherwise the room state isn't updated yet (and we can't for
example distinguish a leave from a kick)

this is only used for updating the UI,
seems safe to emit this event at a later point
2019-04-16 17:16:24 +02:00
Travis Ralston b95079d1c5 Merge pull request #898 from matrix-org/travis/packages.matrix.org
Use packages.matrix.org for Olm
2019-04-16 09:14:21 -06:00
J. Ryan Stinnett e59f36cdc0 Merge pull request #899 from matrix-org/travis/fix-tests
Fix tests on develop
2019-04-16 09:36:56 +01:00
Travis Ralston 0f2f041d8b Use constants for autodiscovery errors
To ease usage
2019-04-15 22:04:24 -06:00
Travis Ralston 33d2837ec3 Use packages.matrix.org for Olm
See https://github.com/vector-im/riot-web/issues/9497
2019-04-15 21:23:18 -06:00
Travis Ralston 7cede221de Support being fed a .well-known config object for validation
Used by Riot to consume the user's provided config. This also includes a change to carry over custom keys on m.homeserver and m.identity_server which aren't intentionally controlled.
2019-04-15 21:22:49 -06:00
Travis Ralston fe47435fc7 Fix tests for autodiscovery 2019-04-15 21:12:17 -06:00
Travis Ralston 491226a916 Use the right this in _shouldAbortSync 2019-04-15 21:04:29 -06:00
Travis Ralston deb7433453 Use toMatch for presence events
We don't pass the reference through, so the test fails with toEqual
2019-04-15 20:17:42 -06:00
Travis Ralston 14973a35c2 Use the right error object 2019-04-15 19:46:30 -06:00
Travis Ralston b5779f8654 Merge pull request #895 from matrix-org/travis/stop-client-on-logout
Stop syncing when the token is invalid
2019-04-15 11:30:24 -06:00
Travis Ralston f7d1984257 De-duplicate usage of shouldAbortSync 2019-04-15 11:29:55 -06:00
Bruno Windels 3fba683090 Merge pull request #887 from jkasun/fix_redact_put
change event redact,  POST request to PUT request
2019-04-15 15:13:59 +00:00
jkasun 430da8ac09 JS Doc Fix 2019-04-15 20:34:39 +05:30
Travis Ralston dcd9b5c382 Stop syncing when the token is invalid
Fixes https://github.com/vector-im/riot-web/issues/9451
2019-04-14 21:38:51 -06:00
jkasun 348c293962 Argument Length Check, Duplicate Fix for Redact Funcation 2019-04-13 22:23:57 +05:30
Travis Ralston 34309da10c Merge pull request #894 from matrix-org/travis/guests/better-errors
Expose better autodiscovery error messages
2019-04-12 10:21:05 -06:00
Travis Ralston 6db973f430 Expose better autodiscovery error messages
Fixes https://github.com/vector-im/riot-web/issues/7925
2019-04-11 15:50:43 -06:00
J. Ryan Stinnett 9f27bafa62 Merge pull request #892 from jryans/degraded-storage
Explicitly guard store usage during sync startup
2019-04-09 17:12:07 +01:00
Travis Ralston b05136146a Merge pull request #893 from matrix-org/travis/v3-safe
Flag v3 rooms as safe
2019-04-09 10:00:56 -06:00
Travis Ralston 13d3be637b Copyright 2019 NV 2019-04-09 09:58:33 -06:00
J. Ryan Stinnett 44de7fad6f Preserve previous error flow 2019-04-09 16:44:36 +01:00
J. Ryan Stinnett b99243406b Move logs inside try block 2019-04-09 16:40:13 +01:00
Travis Ralston 0e9ec811b0 Merge pull request #890 from matrix-org/travis/cached-capabilities
Cache failed capabilities lookups for shorter amounts of time
2019-04-09 09:29:42 -06:00
Travis Ralston 6979177fb2 Log errors for capabilities requests 2019-04-09 09:27:38 -06:00
Travis Ralston 3aa8bfa6ca Flag v3 rooms as safe
The spec says they are, so we might as well too.
2019-04-09 09:25:07 -06:00
Travis Ralston 17b356b08e Merge pull request #891 from matrix-org/travis/fix-notifs
Fix highlight notifications for unencrypted rooms
2019-04-09 09:19:58 -06:00
Travis Ralston f72ae490a8 Appease the linter 2019-04-09 09:05:20 -06:00
J. Ryan Stinnett b0c3d0d2e3 Explicitly guard store usage during sync startup
This adds explicit `try` blocks in the spots where we interact with the store
during sync startup. This shouldn't be necessary as the store should already be
catching this and degrading as of
https://github.com/matrix-org/matrix-js-sdk/pull/884, but that doesn't seem to
have been enough for the affected user in
https://github.com/vector-im/riot-web/issues/7769, as they are seeing sync just
stop when storing without any further detail.
2019-04-09 15:06:54 +01:00
Travis Ralston 9dc344999e Fix highlight notifications for unencrypted rooms
A logic error introduced by https://github.com/matrix-org/matrix-js-sdk/pull/886 meant that all unencrypted rooms were not getting highlight notifications.
2019-04-08 15:57:44 -06:00
Travis Ralston 663c096400 Cache failed capabilities lookups for shorter amounts of time
This should fix https://github.com/vector-im/riot-web/issues/9225 for showing up too often/too long.
2019-04-08 12:24:00 -06:00
J. Ryan Stinnett 420b4d119d Merge pull request #889 from jryans/guard-missing-crypto
Document checking crypto state before using `hasUnverifiedDevices`
2019-04-08 16:56:13 +01:00
J. Ryan Stinnett 58b752c63b Document checking crypto state before using hasUnverifiedDevices
It's unclear what `hasUnverifiedDevices` should do when crypto is disabled on
the current device. Let's at least document that callers should first check
crypto status.
2019-04-08 16:24:25 +01:00
J. Ryan Stinnett 4740232fa4 Merge pull request #888 from jryans/degraded-storage
Add logging to sync startup path
2019-04-08 16:20:29 +01:00
J. Ryan Stinnett 0cc9994b8b Add logging to sync startup path
In https://github.com/vector-im/riot-web/issues/7769, we're seeing sync startup
fail to complete, but the actual error isn't being logged. Hopefully these extra
debug logs will provide more insight into the failing step.
2019-04-08 15:55:52 +01:00
Bruno Windels 1e78628b23 Merge branch 'master' into develop 2019-04-08 16:04:45 +02:00
Bruno Windels 00f5ddc93c v1.0.4 2019-04-08 16:03:26 +02:00
Bruno Windels f79f2105fd Prepare changelog for v1.0.4 2019-04-08 16:03:25 +02:00
Travis Ralston 828c51467f Refuse to set forwards pagination token on live timeline
Should fix the error seen in https://github.com/matrix-org/riot-web-rageshakes/issues/1389 (https://github.com/vector-im/riot-web/issues/8593)
2019-04-08 15:48:03 +02:00
Travis Ralston 963e271bce Refuse to link live timelines into the forwards/backwards position
See https://github.com/vector-im/riot-web/issues/8593#issuecomment-478681816

Previously (https://github.com/matrix-org/matrix-js-sdk/pull/873) we allowed half-linking timelines to each other if they satisfy the conditions, however this appears to not be helping. Instead, it seems like the timelines are getting stuck in a position where one direction is spliced but the other is broken. To avoid this case, we'll just avoid splicing in both directions when one of the directions is invalid.
2019-04-08 15:47:40 +02:00
Travis Ralston 0ab41215f0 Add a tiny bit of logging to work out what timelines are doing
See https://github.com/vector-im/riot-web/issues/8593
2019-04-08 15:47:21 +02:00
Travis Ralston d153c4da07 log the timeline that broke 2019-04-08 15:47:03 +02:00
Travis Ralston 91aa783c3d Use better words for warnings 2019-04-08 15:46:56 +02:00
Travis Ralston a54845bf76 Appease the linter 2019-04-08 15:46:48 +02:00
Travis Ralston 8a56a5f1ed Refuse splicing the live timeline into a broken position
Credit to Matthew for basically solving this.

Theoretically fixes spontaneous timeline corruption: https://github.com/vector-im/riot-web/issues/8593

When the live timeline ends up in a position where it can no longer be live (such as becoming the second timeline in the set, rather than the first) we end up getting neighbouring timeline errors. By refusing to splice the live timeline into such a position, we hopefully keep the live timeline in a position of still being live for when it is next used.

The running theory that leads to this fix is multiple limited syncs coming in, causing holes in the timeline. When trying to patch up the holes, the timeline set would end up splicing all over the place, leading to potentially splicing the live timeline into a broken position.
2019-04-08 15:46:41 +02:00
Travis Ralston f585c80491 Merge pull request #886 from matrix-org/travis/e2e-notifs-2
Track e2e highlights better, particularly in 'Mentions Only' rooms
2019-04-08 07:40:31 -06:00
jkasun c495b12cef change event redact, POST request to PUT request 2019-04-07 00:19:02 +05:30
Travis Ralston 1d6f7f862f Track e2e highlights better, particularly in 'Mentions Only' rooms
Fixes https://github.com/vector-im/riot-web/issues/9280

The server is unable to calculate encrypted highlights for us, so we calculate them. This also means the server always sends a zero for highlight_count, and therefore in sync.js we now trust our judgement over the server's. In future, this check will need to be altered to support server-side encrypted notifications if that happens. This fixes the part of 9280 where the badge count ends up disappearing unless the message received also happens to be a mention.

The changes in client.js are more to support rooms which are mentions only. Because the server doesn't send an unread_count for these rooms, the total notifications will always be zero. Therefore, we try and calculate that. In order to do that, we need to assume that our highlight count is also wrong and calculate it appropriately.
2019-04-05 14:49:38 -06:00
Hubert Chathi 0a82c84006 Merge pull request #882 from uhoreg/fix_mac
support both the incorrect and correct MAC methods
2019-04-05 16:45:45 -04:00
Travis Ralston a614a02b90 Merge pull request #885 from matrix-org/travis/refuse-forwards-token
Refuse to set forwards pagination token on live timeline
2019-04-05 11:54:33 -06:00
Travis Ralston 0945e2c5c6 Refuse to set forwards pagination token on live timeline
Should fix the error seen in https://github.com/matrix-org/riot-web-rageshakes/issues/1389 (https://github.com/vector-im/riot-web/issues/8593)
2019-04-05 11:36:01 -06:00
J. Ryan Stinnett b1b49413d0 Merge pull request #884 from jryans/degraded-storage
Degrade `IndexedDBStore` back to memory only on failure
2019-04-05 11:32:42 +01:00
Hubert Chathi 01af303d63 fix the selection of the verification methods, and test more things 2019-04-04 14:08:30 -04:00
Hubert Chathi 751060305c update the name of the MAC method 2019-04-04 14:07:16 -04:00
J. Ryan Stinnett 389fcfaf3d Ensure IDB store maintains all memory state
A few of the IDB store methods weren't updating memory store state, so let's
improve those so we can reliably fall back to it from IDB at any time.
2019-04-04 17:39:50 +01:00
J. Ryan Stinnett 3eb0c534a5 Clarify why it's safe to change store types on demand 2019-04-04 15:54:26 +01:00
J. Ryan Stinnett 8a2f84b678 Emit event when IndexedDBStore degrades
This allows for optional tracking of when the store degrades to see how often it
happens in the field.
2019-04-04 12:06:41 +01:00
J. Ryan Stinnett dd00735409 Degrade IndexedDBStore back to memory only on failure
IndexedDB may fail at any moment with `QuoteExceededError` when space is low or
other random issues we can't control. Since `IndexedDBStore` is just a cache for
improving performance, we can give up on it if it fails.

This causes `IndexedDBStore` to degrade in place back to using memory only. This
allow (for example) login to complete even if IndexedDB is exploding.

Hopefully improves https://github.com/vector-im/riot-web/issues/7769
2019-04-04 12:06:41 +01:00
J. Ryan Stinnett 6ba7e85e24 Ensure we have crypto before accessing it in Room model
If crypto startup has failed, we shouldn't try to access any of its methods.
This fixes a variant of this in the `Room` model.
2019-04-04 12:06:41 +01:00
Hubert Chathi 32814d1833 Merge branch 'develop' into dbkr/cross_signing 2019-04-03 19:28:51 -04:00
Travis Ralston 941d93c2f4 Merge pull request #877 from matrix-org/travis/tlexpl-full-abort
Refuse to link live timelines into the forwards/backwards position when either is invalid
2019-04-03 09:59:09 -06:00
David Baker c73e9cbc7c Merge pull request #883 from matrix-org/dbkr/logging_for_9357
Key backup logging improvements
2019-04-03 11:45:10 +01:00
David Baker ae89e4bf21 Key backup logging improvements
To try & diagnose https://github.com/vector-im/riot-web/issues/9357
2019-04-03 11:00:31 +01:00
Hubert Chathi d1e64d0cfb support both the incorrect and correct MAC methods
also do some refactoring to make it easier to support choices in the other
methods in the future
2019-04-02 23:36:49 -04:00
David Baker 8f9f9590d9 Merge pull request #880 from matrix-org/dbkr/more_logging_for_7769
Don't assume aborts are always from txn.abort()
2019-04-02 18:33:32 +01:00
David Baker ed68093310 lint 2019-04-02 18:30:49 +01:00
David Baker 23655e748d Don't assume aborts are always from txn.abort()
According to https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction/abort_event
a txn can abort for any number of reasons, not just because of an
abort call, so we can't assume our abort error is set.

This should give us more info on https://github.com/vector-im/riot-web/issues/7769
2019-04-02 18:25:23 +01:00
David Baker 98624871bd Merge pull request #878 from matrix-org/dbkr/logging_for_7769
Add a bunch of logging
2019-04-02 13:34:26 +01:00
Will Hunt 7b154c0834 Revert "Remove "Event sent to" line from client.js"
This reverts commit d9f056242f.
2019-04-02 13:24:58 +01:00
Will Hunt d9f056242f Remove "Event sent to" line from client.js
Emitting a log line here means that bridges emit this for each event sent in stdout, which bloats forever.log
2019-04-02 13:22:27 +01:00
David Baker a4268d288e Add a bunch of logging
to try & diagnose https://github.com/vector-im/riot-web/issues/7769
2019-04-02 12:30:24 +01:00
Travis Ralston f90c91dded Refuse to link live timelines into the forwards/backwards position
See https://github.com/vector-im/riot-web/issues/8593#issuecomment-478681816

Previously (https://github.com/matrix-org/matrix-js-sdk/pull/873) we allowed half-linking timelines to each other if they satisfy the conditions, however this appears to not be helping. Instead, it seems like the timelines are getting stuck in a position where one direction is spliced but the other is broken. To avoid this case, we'll just avoid splicing in both directions when one of the directions is invalid.
2019-04-01 18:43:48 -06:00
Travis Ralston 5c8890c3c1 Add a tiny bit of logging to work out what timelines are doing
See https://github.com/vector-im/riot-web/issues/8593
2019-04-01 11:11:35 -06:00
David Baker cd3c6809a9 Merge branch 'master' into develop 2019-04-01 13:33:51 +01:00
David Baker e2c17528c2 v1.0.3 2019-04-01 13:31:45 +01:00
David Baker e00f565f37 Prepare changelog for v1.0.3 2019-04-01 13:31:44 +01:00
J. Ryan Stinnett 085e797c30 Merge pull request #874 from matrix-org/travis/1.0.6-fire/872
Add existence check to local storage based crypto store
2019-04-01 09:26:39 +01:00
J. Ryan Stinnett d753db590b Add existence check to local storage based crypto store
This supports additional diagnostics of stores in
https://github.com/vector-im/riot-web/issues/9309.
2019-03-29 16:19:54 -06:00
Travis Ralston eab074a27b Merge pull request #873 from matrix-org/travis/refuse-splicing
Refuse splicing the live timeline into a broken position
2019-03-28 19:20:58 -06:00
Travis Ralston e2a3e3816f log the timeline that broke 2019-03-28 19:14:17 -06:00
Travis Ralston 01dd57adab Use better words for warnings 2019-03-28 18:59:40 -06:00
Travis Ralston 08e674b695 Appease the linter 2019-03-28 18:34:57 -06:00
Travis Ralston 9f70970e61 Refuse splicing the live timeline into a broken position
Credit to Matthew for basically solving this.

Theoretically fixes spontaneous timeline corruption: https://github.com/vector-im/riot-web/issues/8593

When the live timeline ends up in a position where it can no longer be live (such as becoming the second timeline in the set, rather than the first) we end up getting neighbouring timeline errors. By refusing to splice the live timeline into such a position, we hopefully keep the live timeline in a position of still being live for when it is next used.

The running theory that leads to this fix is multiple limited syncs coming in, causing holes in the timeline. When trying to patch up the holes, the timeline set would end up splicing all over the place, leading to potentially splicing the live timeline into a broken position.
2019-03-28 18:30:41 -06:00
J. Ryan Stinnett e9ffd5a125 Merge pull request #872 from jryans/storage-existence-check-ls-crypto
Add existence check to local storage based crypto store
2019-03-28 17:49:08 +00:00
J. Ryan Stinnett 20f4469361 Add existence check to local storage based crypto store
This supports additional diagnostics of stores in
https://github.com/vector-im/riot-web/issues/9309.
2019-03-28 12:20:51 +00:00
David Baker feac096dc2 v1.0.3-rc.1 2019-03-27 17:29:28 +00:00
David Baker 67985d449a Prepare changelog for v1.0.3-rc.1 2019-03-27 17:29:27 +00:00
J. Ryan Stinnett a6de59c198 Update matrix-mock-request 2019-03-27 15:02:48 +00:00
J. Ryan Stinnett 01eeb98e35 Merge pull request #871 from jryans/storage-edge-cases
Add IndexedDB existence checks
2019-03-26 15:32:23 +00:00
J. Ryan Stinnett 49a7defbf0 Close the DB right away
This seems to be needed in Chrome to avoid blocking future opens.
2019-03-26 14:58:45 +00:00
J. Ryan Stinnett c1ba5de686 Add missing word 2019-03-26 11:25:59 +00:00
J. Ryan Stinnett 81428f23d1 Tweak code style 2019-03-26 11:21:22 +00:00
Travis Ralston 8af86bb746 Merge pull request #869 from matrix-org/travis/timeline-explosion-workaround
Emit sync errors for capturing by clients
2019-03-25 11:33:01 -06:00
J. Ryan Stinnett 8513f5c413 Add an IndexedDB existence check to the crypto store
This will be useful for future storage diagnostics as part of
https://github.com/vector-im/riot-web/issues/9271.
2019-03-25 16:11:59 +00:00
J. Ryan Stinnett eadec35093 Add an IndexedDB existence check to the main store
This will be useful for future storage diagnostics as part of
https://github.com/vector-im/riot-web/issues/9271.
2019-03-25 16:11:51 +00:00
Travis Ralston d6dbd621b8 Merge pull request #868 from matrix-org/travis/leave-room-chain
Add functions for getting room upgrade history and leaving those rooms
2019-03-25 09:57:10 -06:00
Travis Ralston 7168f76614 Emit sync errors for capturing by clients
For https://github.com/vector-im/riot-web/issues/9260
2019-03-22 20:19:38 -06:00
Travis Ralston 1cda95f23c Add functions for getting room upgrade history and leaving those rooms
Required for https://github.com/vector-im/riot-web/issues/8539
2019-03-22 17:34:05 -06:00
J. Ryan Stinnett bb1cd2bbce Merge pull request #867 from jryans/real-name
Clarify the meaning of 'real name' for contribution
2019-03-22 17:26:21 +00:00
J. Ryan Stinnett 3f90ac5712 Merge pull request #865 from jryans/storage-edge-cases
Remove `sessionStore` to `cryptoStore` migration path
2019-03-22 17:25:51 +00:00
J. Ryan Stinnett 8d249a843c Reformat contribution doc to 80 chars 2019-03-22 11:53:43 +00:00
J. Ryan Stinnett 858b41d835 Clarify the meaning of 'real name' for contribution
This applies the clarification already adopted by Synapse:

https://github.com/matrix-org/synapse/commit/ec766b25303b420850e6d2875f156f23109acf6a
2019-03-22 11:51:07 +00:00
Travis Ralston 61aea05af0 Merge pull request #866 from matrix-org/travis/verbose-capabilities
Add debugging for spurious room version warnings
2019-03-20 17:10:23 -06:00
Travis Ralston e7c764d5f5 Add debugging for spurious room version warnings
See https://github.com/vector-im/riot-web/issues/9225
2019-03-20 17:05:09 -06:00
J. Ryan Stinnett 09a9afe4e7 Clarify the current state of each store
This moves docs about the 3 stores used next to each other and clarifies their
purpose for future readers.
2019-03-20 16:56:47 +00:00
J. Ryan Stinnett 5a26503da7 Remove sessionStore to cryptoStore migration path
The code to migrate from the `sessionStore` to `cryptoStore` originally appeared
in https://github.com/matrix-org/matrix-js-sdk/pull/584 (2017-12-06). At this
point, it seems safe to assume most sessions that need migrating have already
done so. Removing this code simplifies store handling and removes the
`sessionStore` from most places in JS SDK.
2019-03-20 16:56:47 +00:00
J. Ryan Stinnett 5faf5ea1f8 Merge pull request #864 from jryans/storage-notes
Add investigation notes for browser storage
2019-03-20 16:35:13 +00:00
J. Ryan Stinnett 0754c29c22 Add investigation notes for browser storage
This collects my notes from investigating the state of browser storage as part
of https://github.com/vector-im/riot-web/issues/9109.
2019-03-20 16:33:47 +00:00
Hubert Chathi d5c6dcf111 Merge pull request #862 from uhoreg/check_resolver
make sure resolve object is defined before calling it
2019-03-20 10:32:10 -04:00
J. Ryan Stinnett 6a57ddd33c Merge pull request #861 from matrix-org/jryans/storage-edge-cases
Rename `MatrixInMemoryStore` to `MemoryStore`
2019-03-20 11:05:55 +00:00
Hubert Chathi bd711cdc1f make sure resolve object is defined before calling it 2019-03-19 23:21:49 -04:00
J. Ryan Stinnett e669e493c9 Add deprecation notice to MatrixInMemoryStore 2019-03-19 15:04:05 +00:00
J. Ryan Stinnett 48f290196c Rename MatrixInMemoryStore to MemoryStore
None of the other store classes use the `Matrix` prefix, and I find the mismatch
confusing (it leads me to think it might have a different purpose than the
others).

This change removes the prefix from the store for consistency. The old name is
left as an export for existing SDK consumers.
2019-03-19 14:24:47 +00:00
David Baker f8985dbb39 Merge branch 'master' into develop 2019-03-18 13:50:07 +00:00
David Baker ef594d52e4 v1.0.2 2019-03-18 13:47:54 +00:00
David Baker 23bbb2f8c6 Prepare changelog for v1.0.2 2019-03-18 13:47:53 +00:00
Travis Ralston 42f181cc7b Appease the linter 2019-03-15 14:11:29 -06:00
Travis Ralston b3d2d39b60 Add tombstone rule as a default rule in support of MSC1930
Part of https://github.com/vector-im/riot-web/issues/8447

See also https://github.com/matrix-org/matrix-doc/pull/1930
2019-03-15 14:07:15 -06:00
Travis Ralston e323d917a4 Support default push rules for when servers are outdated 2019-03-15 14:06:28 -06:00
Travis Ralston 73c7733ebc Merge pull request #859 from matrix-org/travis/buildkite
Use Buildkite for CI
2019-03-15 11:43:50 -06:00
Travis Ralston 87f7f9443e Delete Travis (CI)
We now use BuildKite
2019-03-13 17:27:17 -06:00
Travis Ralston af6bbbc59b Add a basic BuildKite :pipeline: 2019-03-13 17:23:30 -06:00
J. Ryan Stinnett 5b35a364a9 Ignore package-lock.json in case of confused npm users 2019-03-13 15:49:18 +00:00
Hubert Chathi d56ebadbc4 Merge pull request #857 from uhoreg/fix_ensureolm_race
only create one session at a time per device
2019-03-13 11:21:35 -04:00
J. Ryan Stinnett 04accdeddc Ignore *.log files, such as from npm or Yarn 2019-03-13 14:40:42 +00:00
David Baker 70575f9e33 v1.0.2-rc.1 2019-03-13 14:24:57 +00:00
David Baker 8e16586d84 Merge branch 'develop' into release-v1.0.2 2019-03-13 14:24:26 +00:00
David Baker 6920dfb800 Yarn needs --new-version to create a new version 2019-03-13 14:23:51 +00:00
David Baker 02d93770aa Prepare changelog for v1.0.2-rc.1 2019-03-13 14:20:18 +00:00
David Baker cd124231c5 Merge pull request #858 from jryans/yarn-ci
Use modern Yarn version on Travis CI
2019-03-13 13:22:39 +00:00
J. Ryan Stinnett cd75848882 Use modern Yarn version on Travis CI
Travis CI uses a quite old version of Yarn by default. This adds Yarn's
recommended incantation for using the latest stable version.
2019-03-13 12:28:07 +00:00
J. Ryan Stinnett 1bae15ede9 Recommend using the latest Node LTS version 2019-03-13 10:50:58 +00:00
Hubert Chathi 8c2001adbf don't reject on error
because we ignore it anyways, and it makes an unrelated unit test fail
2019-03-12 17:24:34 -04:00
Hubert Chathi 79ca235e7c only create one session at a time per device 2019-03-12 16:04:26 -04:00
J. Ryan Stinnett 4570fcaa8a Ignore .npmrc 2019-03-12 12:02:04 +00:00
J. Ryan Stinnett 90670cf1be Merge pull request #856 from jryans/yarn
Switch to `yarn` for dependency management
2019-03-11 13:27:04 +00:00
J. Ryan Stinnett cc86f427d2 Convert Olm to a dev dependency
This gives more natural behavior of downloading Olm by default when working on
this module, so that all tests including crypto are run.
2019-03-08 15:58:08 +00:00
J. Ryan Stinnett 2144791d52 Update scripts and docs to use yarn where appropriate
Most `npm` operations are replaced with `yarn`, which generally has better
behavior. However, steps like publish that write to the NPM registry are left to
`npm`, which currently handles these tasks best.
2019-03-08 15:45:57 +00:00
J. Ryan Stinnett 33aabf44e7 Convert from npm to yarn lock file
The npm lock file was imported into yarn. A yarn install pass was then run to
double-check the lock file for sanity.
2019-03-08 10:47:58 +00:00
Hubert Chathi fc1ea27380 Merge pull request #855 from uhoreg/fix_key_requests
More key request fixes
2019-03-06 14:46:43 -05:00
Hubert Chathi 81946294d8 use lolex to fake the timer 2019-03-06 14:42:52 -05:00
Matthew Hodgson 77270fa78c Merge pull request #851 from matrix-org/travis/e2e-notifs
Calculate encrypted notification counts
2019-03-06 17:11:22 +00:00
Hubert Chathi 9e29289dcc use a different transaction ID when re-sending a key request 2019-03-06 12:02:48 -05:00
Travis Ralston 6198943976 Add a mention that we should be handling gaps in /sync 2019-03-06 09:56:36 -07:00
David Baker 8beb836ccd Merge pull request #854 from matrix-org/dbkr/deps_190206
Update dependencies
2019-03-06 14:08:26 +00:00
David Baker b7c0e39c1a Update dependencies
To get non-vulnerable versions
2019-03-06 13:50:35 +00:00
David Baker 777acae2e5 Merge branch 'master' into develop 2019-03-06 11:17:25 +00:00
David Baker e77389c1ce v1.0.1 2019-03-06 11:10:42 +00:00
David Baker eb24e2e1f1 Prepare changelog for v1.0.1 2019-03-06 11:10:41 +00:00
Hubert Chathi 114244f8bb fix typo 2019-03-06 00:23:01 -05:00
Travis Ralston 54769d9136 Add jsdoc 2019-03-05 14:25:32 -07:00
Travis Ralston 2f2deb5333 Expose the clear event content directly from an event 2019-03-05 14:04:51 -07:00
Travis Ralston 37f106d4af More safely set the push actions for an encrypted event 2019-03-05 14:04:39 -07:00
David Baker 36ee7cdbfc v1.0.1-rc.2 2019-03-05 18:53:16 +00:00
David Baker 2b564498ee Prepare changelog for v1.0.1-rc.2 2019-03-05 18:53:15 +00:00
David Baker 0ddba16fa1 Merge pull request #853 from matrix-org/bwindels/dontswallowcryptotxnerrors
dont swallow txn errors in crypto store
2019-03-05 18:36:15 +00:00
Hubert Chathi 550086eb67 Merge pull request #850 from uhoreg/ensure_key_request
make sure key requests get sent
2019-03-05 10:57:15 -05:00
Hubert Chathi 055ce673cd fix jsdoc 2019-03-05 10:54:02 -05:00
David Baker 5308595658 Merge pull request #852 from matrix-org/dbkr/getusermedia_deviceid_ideal
Use 'ideal' rather than 'exact' for deviceid
2019-03-05 14:23:39 +00:00
David Baker e726e29f39 Better logging here too 2019-03-05 13:02:55 +00:00
David Baker 33b12fa6b5 Use 'ideal' rather than 'exact' for deviceid
We were using 'exact' which means we fail outright if the device
we wanted isn't available. This means if a user selects a specific
device then later unplugs it, we fail to open a capture device
the next time they make a call even if there's one available.
Using 'ideal' uses the chosen device in preference, but something
else if it isn't available.

Also log the name of the exception when we fail to open a capture
device to give us more of an idea of what's gone wrong.

Should help fix https://github.com/vector-im/riot-web/issues/8993
2019-03-05 12:59:29 +00:00
Travis Ralston 829cd05cba Appease the linter 2019-03-04 21:46:48 -07:00
Travis Ralston 4834e12a3a Calculate unread badges for encrypted events 2019-03-04 21:41:20 -07:00
Travis Ralston bcd4ad130c Use the decrypted event content when checking the push rules
Otherwise we'll be looking at the encrypted source, and that doesn't help anyone.
2019-03-04 21:33:57 -07:00
Travis Ralston 998d9e010e Support flushing the cache on calculated push rules
Needed for encrypted events to be able to pass some push rules.
2019-03-04 21:24:25 -07:00
Hubert Chathi 5480e8e1d5 refactor key sharing requests
use sendRoomKeyRequest with a new resend flag, instead of cancelRoomKeyRequest,
when requesting keys, so that we make sure that we send a new request if there
is no previous request

fixes https://github.com/vector-im/riot-web/issues/6838
2019-03-04 17:09:56 -05:00
Hubert Chathi 98fdcabc00 stop client after each test 2019-03-04 16:59:54 -05:00
Bruno Windels 236397816d Merge pull request #849 from matrix-org/bwindels/dontswallowcryptotxnerrors
Don't swallow txn errors in crypto store
2019-03-04 13:05:17 +01:00
Bruno Windels 755c55de3e dont swallow txn errors in crypto store 2019-03-04 12:58:05 +01:00
Hubert Chathi 526da71992 Merge pull request #848 from uhoreg/fix_partial_keyshare
handle partially-shared sessions better
2019-03-01 12:18:54 -05:00
Hubert Chathi 86ef262799 fix c+p comment 2019-03-01 12:15:59 -05:00
Hubert Chathi 282904d4be restore the order of backup vs retry decryption 2019-03-01 08:54:04 -05:00
Hubert Chathi a1be24307a lint 2019-02-28 22:54:46 -05:00
Hubert Chathi 4b5623691b handle partially-shared sessions better
- don't cancel key requests if we can't decrypt everything in the session
- overwrite the session key if we get a better version
2019-02-28 16:01:29 -05:00
David Baker 7bdf1e9b92 v1.0.1-rc.1 2019-02-28 14:28:36 +00:00
David Baker af1db8a606 Prepare changelog for v1.0.1-rc.1 2019-02-28 14:28:36 +00:00
Travis Ralston a99bb3c4c9 Merge pull request #847 from matrix-org/travis/fix-megolm-error
Fix "e is undefined" masking the original error in MegolmDecryption
2019-02-26 13:59:16 -07:00
Travis Ralston fd155c15bd Excessive checks are excessive 2019-02-26 13:50:09 -07:00
Travis Ralston aaa43631aa Fix "e is undefined" masking the original error in MegolmDecryption 2019-02-26 13:15:03 -07:00
Hubert Chathi d2557bc943 only set the dirty flag if something was actually changed 2019-02-22 08:37:34 -05:00
Hubert Chathi 33a3506981 speling is hard 2019-02-15 17:14:29 -05:00
J. Ryan Stinnett 03a54353be v1.0.0 2019-02-14 16:48:12 +00:00
J. Ryan Stinnett c6328923e6 Prepare changelog for v1.0.0 2019-02-14 16:48:11 +00:00
J. Ryan Stinnett 1ecb820bb0 Merge branch 'develop' into release-v1.0.0 2019-02-14 16:46:34 +00:00
J. Ryan Stinnett 0be2319288 Merge pull request #841 from jryans/package-lock-release
Try again to commit package-lock.json
2019-02-14 15:57:21 +00:00
J. Ryan Stinnett 073a025b83 Try again to commit package-lock.json 2019-02-14 14:20:31 +00:00
J. Ryan Stinnett e83836d487 v1.0.0-rc.2 2019-02-14 10:47:41 +00:00
J. Ryan Stinnett 065c61e05c Prepare changelog for v1.0.0-rc.2 2019-02-14 10:47:41 +00:00
J. Ryan Stinnett 139a6bd903 Merge branch 'develop' into release-v1.0.0 2019-02-14 10:43:04 +00:00
J. Ryan Stinnett 3fa0ee59d4 Merge pull request #839 from matrix-org/dbkr/commit_package_lock
Release script: commit package-lock.json
2019-02-13 18:57:38 +00:00
David Baker bd3d26422d git st is not a standard thing 2019-02-13 18:20:16 +00:00
David Baker 370ef9fc69 Merge pull request #840 from matrix-org/dbkr/recheck_key_backup
Add method to force re-check of key backup
2019-02-13 18:19:35 +00:00
David Baker a087fb37a3 Add method to force re-check of key backup
Also detect when the key backup version changes and do the right
thing

https://github.com/vector-im/riot-web/issues/8524
2019-02-13 15:40:07 +00:00
David Baker 68c8fe0fa9 Release script: commit package-lock.json
Commit the package-lock.json when bumping the version, otherwise
the versions get out of sync, and this is going to matter more now
that jenkins runs `npm ci` which is fussy about these things.
2019-02-13 13:40:46 +00:00
Bruno Windels 4309749979 Merge pull request #838 from matrix-org/bwindels/e2eiconsanddialog
Fix: dont check for unverified devices in left members
2019-02-13 11:41:57 +01:00
Bruno Windels 1a677804a4 use getEncryptionTargetMembers instead of doing membership checks ourselves 2019-02-13 11:22:38 +01:00
Bruno Windels a427e2a75c dont check devices for left members 2019-02-12 18:33:44 +01:00
David Baker 3c735b0ac1 v1.0.0-rc.1 2019-02-08 18:33:52 +00:00
David Baker 4f446c3909 Prepare changelog for v1.0.0-rc.1 2019-02-08 18:33:51 +00:00
Hubert Chathi 999ed1b5b3 Merge pull request #837 from uhoreg/emoji_sas
change hex SAS verification to decimal and emoji
2019-02-08 11:44:53 -05:00
David Baker 8fa19f4a0f More en_GBification 2019-02-08 14:56:08 +00:00
David Baker 71a01ec234 Replace symbol characters with the emoji variants
The symbol ones are often rendered as little black & white icons: replace them with their variants from the emoji range

Also change to en_GB
2019-02-08 14:11:47 +00:00
David Baker 32f033a9da Merge pull request #836 from matrix-org/dbkr/trust_on_decrypt
Trust on decrypt
2019-02-08 11:36:44 +00:00
David Baker dade385147 Put error constant on the class 2019-02-08 10:43:17 +00:00
David Baker 6cf2e54f9a Fix double-santa 2019-02-08 09:59:42 +00:00
Hubert Chathi fb673b0304 change hex SAS verification to decimal and emoji 2019-02-08 00:56:30 -05:00
David Baker 1a425af3f2 Pass backup info here too 2019-02-07 15:51:51 +00:00
David Baker 9bafed2c26 OK fine, we'll use the session store 2019-02-07 15:33:49 +00:00
David Baker bb2d0b0f62 lint 2019-02-07 14:47:03 +00:00
David Baker 5e4f10a80c Trust on decrypt
Trust backups that we've restored by saving the matching pubkey
locally.

NB. Contains technically breaking API changes to the backup restore
(takes backupInfo rather than version).
2019-02-07 14:37:25 +00:00
David Baker 9e12fc4d7d Merge pull request #835 from matrix-org/dbkr/always_track_own_device_list
Always track our own devices
2019-02-07 09:30:13 +00:00
David Baker 1caf2b7f83 Always track our own devices
It's generally a reasonable assumption that we'll be interested in
them, and important for key backup.

Fixes https://github.com/vector-im/riot-web/issues/8213
2019-02-06 16:48:57 +00:00
David Baker e54f71718f Olm pre2 for cross-signing 2019-02-05 13:41:14 +00:00
David Baker 6f17e3e659 Merge pull request #834 from matrix-org/dbkr/lint_consistency
Make linting rules more consistent
2019-02-05 13:12:25 +00:00
David Baker 7f5584e4f5 All the linting 2019-02-05 13:03:27 +00:00
David Baker 17e2cd755d Make linting rules more consistent
* Put back babel-eslint for class-properties
 * Allow arrow functions without params

This makes the style more consistent with react-sdk.

NB. The line lengths are still inconsistent but it's not clear which
way to go on that yet.
2019-02-05 11:58:53 +00:00
David Baker b3513dc8f8 Make linting rules more consistent
* Put back babel-eslint for class-properties
 * Allow arrow functions without params

This makes the style more consistent with react-sdk.

NB. The line lengths are still inconsistent but it's not clear which
way to go on that yet.
2019-02-05 11:56:08 +00:00
David Baker 1b82dffcb4 Merge remote-tracking branch 'origin/develop' into dbkr/cross_signing 2019-02-01 22:40:14 +00:00
David Baker 5500f0d794 Re-track own device list
Sp we don't stop tracking our own
2019-02-01 22:39:12 +00:00
David Baker c8082535de Always track your own devices
This was causing all the cross-signing stuff to fail and was almost
certainly the cause of https://github.com/vector-im/riot-web/issues/8213
2019-02-01 19:19:00 +00:00
Bruno Windels a6970d4de8 Merge pull request #833 from matrix-org/bwindels/e2eicons
add method to room to check for unverified devices
2019-02-01 18:14:51 +00:00
David Baker 7dedcb82b2 Lint
or at least the rules that are consistent with the rest of our
codebase
2019-02-01 18:12:27 +00:00
David Baker 7195365188 Update package-lock.json
because Travis and npm now have a thing where they combust if your
package-lock is out of sync
2019-02-01 15:59:53 +00:00
David Baker 910d0ec9c1 Sign & trust the key backup from the SSK 2019-02-01 15:49:20 +00:00
Bruno Windels bc99c1f3ce add method to room to check for unverified devices 2019-02-01 14:37:11 +01:00
David Baker 1d58a64ee1 Track SSKs for users
and verify our own against our locally stored private part
2019-02-01 13:04:21 +00:00
David Baker 1f77cc6d1a Cross sign the current device with the SSK
whenever we get the SSK, ie. when creating or restoring a backup
2019-01-31 21:13:01 +00:00
David Baker 02d4dcb128 Store SSK & USK in crypto store
and restore them from the key backup.

NB. This has an interface change to restoreKeyBackup where I've
changed it to take a backupInfo rather than a version (this also
saves us re-fetching the backup metadata in the case of a passphrase
restore).
2019-01-31 15:48:05 +00:00
David Baker 2b54f442d1 Add cross signing key creation into key backup
Start of cross-signing impl
2019-01-30 18:10:40 +00:00
David Baker 5e3ff7fc27 Re-apply changelog that somehow got lost 2019-01-30 13:12:32 +00:00
Bruno Windels ffe3f966fe Merge pull request #831 from matrix-org/experimental
Merge redesign into develop
2019-01-30 12:47:33 +00:00
Travis Ralston c60c19a28e Merge pull request #830 from matrix-org/travis/rver-cap-support
Supporting infrastructure for educated decisions on when to upgrade rooms
2019-01-29 11:28:57 -07:00
Travis Ralston 4ea785b604 Add some prose for what safe versions are 2019-01-29 10:46:40 -07:00
Travis Ralston 2d4e9d0d3f Add safety for when the endpoint doesn't exist 2019-01-28 17:18:57 -07:00
Travis Ralston 971d572fbf Supporting infrastructure for educated decisions on when to upgrade rooms
Part of https://github.com/vector-im/riot-web/issues/8251
2019-01-28 16:03:27 -07:00
Hubert Chathi 244e1b84f7 Initial implementation of key verification 2019-01-23 13:34:25 -05:00
David Baker e5cdc99a34 Merge pull request #826 from matrix-org/dbkr/key_backup_status_unknown_device
Include signature info for unknown devices
2019-01-18 10:58:21 +00:00
David Baker 9a5768219f Doc function API changes 2019-01-18 10:04:22 +00:00
Travis Ralston cee8f57318 Merge pull request #828 from matrix-org/travis/v2-is-safe
Flag v2 rooms as "safe"
2019-01-17 16:36:27 -07:00
Travis Ralston 1a40e0a83a Flag v2 rooms as "safe"
We'll still need something like https://github.com/matrix-org/matrix-doc/pull/1804 to make this work correctly, but this fixes the immediate issue in https://github.com/vector-im/riot-web/issues/8154
2019-01-17 16:14:53 -07:00
Travis Ralston d0072d930f Merge pull request #827 from matrix-org/develop
Develop->Experimental
2019-01-17 15:51:22 -07:00
David Baker 385062c4d7 Include signature info for unknown devices
Add a 'deviceId' property and leave 'device' undefined for unknown
devices.

https://github.com/vector-im/riot-web/issues/8142
2019-01-17 16:43:26 +00:00
J. Ryan Stinnett 9245638b25 Set key backup guard flag to avoid duplicate uploads
Not sure how this was missed, but anyway, guard against duplicate uploads with
the existing flag.
2019-01-17 08:19:09 -06:00
J. Ryan Stinnett 1865542192 Emit when user's status message changes 2019-01-15 08:59:31 -06:00
J. Ryan Stinnett 2563abda11 Ensure we have power to set custom status
If we're in a non-DM room of 2 people, we may not have power to set state events
like custom status.  Ensure that we do before sending.
2019-01-15 08:59:31 -06:00
David Baker 59b80d8fbd Remove babel-eslint as we no longer need it 2019-01-10 10:42:43 +00:00
David Baker 68bb8182e4 Update ESLint
* Bump version
 * Add a couple of rules to match our existing precedent
 * Fix a few genuine lint errors
 * Ignore a guard-for-in (not sure why eslint doesn't like this?)
 * Update max warnings
2019-01-10 10:42:34 +00:00
David Baker c979ff6696 Merge pull request #821 from matrix-org/dbkr/update_eslint
Update ESLint
2019-01-10 09:35:24 +00:00
J. Ryan Stinnett 25681e888c Change initial key backup to background
Alters the APIs used for initial key backup so that the actual upload happens in
the background after all session are marked for backup.
2019-01-09 12:03:55 -06:00
J. Ryan Stinnett 5cfd082b00 Schedule key upload on enabling backup
This ensures a partially completed backup will continue to make progress.
2019-01-09 12:03:55 -06:00
J. Ryan Stinnett 0cbced43bd Emit when count of sessions to backup changes
This will be used in the React SDK to display upload progress when there are
many sessions to upload.
2019-01-09 12:03:55 -06:00
David Baker b3e8d7e07e Remove babel-eslint as we no longer need it 2019-01-09 17:33:42 +00:00
David Baker f4a7395e3a Update ESLint
* Bump version
 * Add a couple of rules to match our existing precedent
 * Fix a few genuine lint errors
 * Ignore a guard-for-in (not sure why eslint doesn't like this?)
 * Update max warnings
2019-01-09 17:29:30 +00:00
J. Ryan Stinnett 14b42abfa4 Re-check key backup status on version mismatch
This ensures we will report the updated status when consumer code asks for it.

Fixes part of https://github.com/vector-im/riot-web/issues/8048.
2019-01-09 04:07:58 -06:00
J. Ryan Stinnett e8022e985e Merge pull request #815 from matrix-org/develop
Merge develop into experimental
2019-01-03 13:54:34 -06:00
David Baker f6c8687dc8 Merge pull request #812 from matrix-org/dbkr/getallsessions
Add a getAllEndToEndSessions to crypto store
2019-01-03 10:09:18 +00:00
J. Ryan Stinnett 472d8faace Merge pull request #668 from matrix-org/t3chguy/fix_displayname_logic
T3chguy/fix displayname logic
2019-01-02 17:40:19 -06:00
Michael Telatynski fc5f3c2fcc re-add empty check after removing hidden chars 2018-12-30 00:20:33 +00:00
Travis Ralston fb756208d8 Merge pull request #814 from trashhalo/rebase-tip
Contributing: Note that rebase lets you mass signoff commits

replace find loop + slice with a single filter. More readable

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

keep public APIs consistent with previous behaviour now that its fuzzy

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

make tests not fail, because of order of occurrence

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

do falsey displayname check regardless of whether we have roomstate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

add tests for the fuzzy disambiguation between members in a room

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

remove duplicated call
2018-12-30 00:18:05 +00:00
Travis Ralston 6a98e93845 Merge pull request #814 from trashhalo/rebase-tip
Contributing: Note that rebase lets you mass signoff commits
2018-12-28 19:40:41 -07:00
Stephen Solka 79e155acfb Note that rebase lets you mass signoff commits
Signed-off-by: Stephen Solka <stephen0q@gmail.com>
2018-12-28 21:36:06 -05:00
David Baker 59ae6e3dc0 Missing bracket 2018-12-21 19:21:40 +00:00
David Baker e628ed3ef4 Add a getAllEndToEndSessions to crypto store
So we can migrate them from place to place
2018-12-21 19:12:01 +00:00
J. Ryan Stinnett 11d40e9daa Merge pull request #672 from matrix-org/t3chguy/unhomoglyph
take into account homoglyphs when calculating similar display names
2018-12-20 14:38:48 +00:00
Michael Telatynski a07f0631b7 Include unhomoglyph in package-lock.json - it must have felt left out :L
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2018-12-19 22:28:40 +00:00
Michael Telatynski 790d1dd8f7 Merge branches 'develop' and 't3chguy/unhomoglyph' of github.com:matrix-org/matrix-js-sdk into t3chguy/unhomoglyph 2018-12-19 22:21:09 +00:00
J. Ryan Stinnett c92e510a4d Pin to base-x 3.0.4
This avoids API changes in 3.0.5 (requiring `Buffer` instances), but more
importantly also avoids dealing with ES6 in dependencies for another day.

Signed-off-by: J. Ryan Stinnett <jryans@gmail.com>
2018-12-18 19:47:17 +00:00
David Baker c48a6c0601 Merge pull request #809 from jryans/key-backup-version-change
Emit for key backup failures
2018-12-18 09:34:43 +00:00
J. Ryan Stinnett 383f3f9834 Merge branch 'develop' into t3chguy/unhomoglyph 2018-12-18 01:01:41 +00:00
J. Ryan Stinnett 3c1e9ba6e9 Merge pull request #646 from leonlianght/develop
emit oldEventId on "updatePendingEvent"
2018-12-18 00:41:52 +00:00
J. Ryan Stinnett 26893b9877 Merge pull request #589 from johbo/add-thirdparty-user
Add getThirdpartyUser to base api
2018-12-18 00:38:09 +00:00
J. Ryan Stinnett 2b734b8e69 Emit for key backup failures
Signed-off-by: J. Ryan Stinnett <jryans@gmail.com>
2018-12-14 15:17:22 +00:00
J. Ryan Stinnett c5f6f87a6c Add await to ensure we wait for backup errors
Signed-off-by: J. Ryan Stinnett <jryans@gmail.com>
2018-12-14 15:17:22 +00:00
David Baker 66cdb62a3d Reorganize key backup flow
This will allow the key backup flow to propagate errors for things like version
mismatches more easily.

In addition, it raises the limit of keys sent per request from 10 to 200 to cut
down on the number of requests.
2018-12-14 15:17:10 +00:00
Travis Ralston f53e33723b Merge pull request #805 from matrix-org/travis/custom-status
Support custom status messages
2018-12-13 10:24:37 -07:00
Travis Ralston 06bc6e7568 Update jsdoc to match _unstable_ prefix 2018-12-13 09:42:22 -07:00
J. Ryan Stinnett 5e3f42ec5a Use olm-loader in all crypto tests
Standardize on importing `olm-loader` rather than pasting the same boilerplate
in different tests.  Importantly, `spec/unit/crypto.spec.js` did not include any
loading approach, so it would only find Olm if some other test loaded it first.

Signed-off-by: J. Ryan Stinnett <jryans@gmail.com>
2018-12-13 08:59:57 +00:00
Travis Ralston 08b3dfa3b5 Prefix the status message API with _unstable
It's not a formal feature of Matrix yet, so we should try and avoid people relying on it. This makes it appear as a private API and is very clearly labeled as not intended for use.
2018-12-12 23:05:03 -07:00
J. Ryan Stinnett 6cf9563441 Avoid checking key backup status if guest
Fixes vector-im/riot-web#7839.

Signed-off-by: J. Ryan Stinnett <jryans@gmail.com>
2018-12-12 22:26:01 +00:00
Travis Ralston fb65c7f4ba Support setting status message in rooms that look like 1:1s
Part of https://github.com/vector-im/riot-web/issues/1528
2018-12-12 13:21:13 -07:00
Travis Ralston c4452909e7 Support .well-known autodiscovery in the js-sdk (#799)
* Support .well-known autodiscovery in the js-sdk

It's much more useful here than in the react-sdk as it can be reused by more applications. This is also required to make the react-sdk a little easier to manage .well-known lookups as soon it'll be doing it in several places. 

Automatic discovery is an abstract concept in the spec and could include more than .well-known in the future, so this is made to be generic enough to support future mechanisms and other resources to discover. There's also a ton of comments (more than normally needed) as people may wish to use this as a reference in their own implementation and it doesn't hurt to explain what everything is doing.

Many of the functions are air lifted from the react-sdk and modified to work within the confines of the js-sdk.

* Swap out uglify-js for uglify-es

So we can start using ES6 dependencies without figuring out how to update babel. 

`uglify-es` is compatible with `uglify-js@3` (we were using `@2`) , which is why the same command is used. This commit includes changes to the command line to make the thing run the same as before too.

* Appease the linter

* Appease the linter some more

* Appease the linter: the tiebreaker

* Appease the linter yet again

* Switch to using the already available URL libraries

* Remove excess logging
2018-12-12 08:38:05 -07:00
Bruno Windels 2d3669b03b Merge pull request #749 from matrix-org/bwindels/releaseimprovements
Extra checks to avoid release script blowing up mid-process.
2018-12-12 09:07:12 +00:00
Travis Ralston 848e6e5897 Support reading custom status messages
Part of https://github.com/vector-im/riot-web/issues/1528
2018-12-11 21:41:15 -07:00
David Baker c723b76138 Merge branch 'master' into develop 2018-12-10 13:37:15 +00:00
David Baker 57f6b0af09 v0.14.2 2018-12-10 13:31:37 +00:00
David Baker 1c4082af45 Prepare changelog for v0.14.2 2018-12-10 13:31:37 +00:00
David Baker eece5d318e Merge pull request #800 from matrix-org/travis/mto-via
Move glob regex utilities out of the pushprocessor and into a more generic place
2018-12-07 17:47:56 +00:00
David Baker bb6ade2165 v0.14.2-rc.1 2018-12-06 11:11:29 +00:00
David Baker 586b010811 Prepare changelog for v0.14.2-rc.1 2018-12-06 11:11:28 +00:00
Travis Ralston 84ab0fde51 Appease the linter 2018-12-05 18:13:28 -07:00
Travis Ralston ec18df2c2a Move glob regex utilities out of the pushprocessor and into a more generic place 2018-12-05 18:01:12 -07:00
David Baker f50503e7c1 Merge pull request #794 from uhoreg/e2e_backup-test-fix
fix some assertions in e2e backup unit test
2018-12-04 11:22:20 +00:00
Travis Ralston c619e5c381 Merge pull request #798 from matrix-org/hs/fix-media-config
Config should be called with auth
2018-12-03 18:29:56 -07:00
Will Hunt e7c4a74ed6 Config should be called with auth 2018-12-04 01:27:08 +00:00
Hubert Chathi f8ea019f02 fix some assertions in e2e backup unit test 2018-11-29 11:52:19 -05:00
David Baker 6db8dd620d Fix https://github.com/matrix-org/matrix-js-sdk/pull/792 2018-11-28 16:11:28 +00:00
David Baker bdc1fa4c03 Merge pull request #792 from matrix-org/dbkr/dont_reestablish_unknwon_device
Don't re-establish sessions with unknown devices
2018-11-28 16:04:31 +00:00
David Baker 4e66a2d436 Don't re-establish sessions with unknown devices
as it won't work
2018-11-28 15:53:13 +00:00
David Baker 0fa948448e Merge branch 'master' into develop 2018-11-22 16:48:36 +00:00
David Baker 76c675cd09 v0.14.1 2018-11-22 16:46:26 +00:00
David Baker 85a4a594c5 Prepare changelog for v0.14.1 2018-11-22 16:46:25 +00:00
David Baker f70746c50f Handle crypto db version upgrades 2018-11-22 16:38:10 +00:00
David Baker 712490b671 Use a vaguely recent node 2018-11-21 18:44:21 +00:00
David Baker b580e68469 Merge pull request #736 from matrix-org/dbkr/e2e_backups
Support for e2e key backups
2018-11-21 18:27:45 +00:00
David Baker bd2cf18fbc Merge pull request #786 from matrix-org/dbkr/e2e_backups_passphrase
Passphrase Support for e2e backups
2018-11-21 18:24:59 +00:00
David Baker 092f4217b0 docs 2018-11-21 17:56:02 +00:00
David Baker abd2ac7168 Rename backup API call in test 2018-11-20 16:34:04 +00:00
David Baker eeea70640e Add randomString factored out from client secret 2018-11-20 16:28:29 +00:00
David Baker 6047838f53 lint 2018-11-20 16:17:58 +00:00
David Baker cb51799246 Make backup restore work 2018-11-20 16:15:29 +00:00
David Baker 44d99277fe Support passphrase-based e2e key backups 2018-11-20 13:09:59 +00:00
David Baker 5b8e643541 Merge branch 'master' into develop 2018-11-19 15:11:40 +00:00
David Baker ae85c209ab v0.14.0 2018-11-19 15:09:43 +00:00
David Baker 2306caa62f Prepare changelog for v0.14.0 2018-11-19 15:09:42 +00:00
Richard van der Hoff 17c11ae23f Merge pull request #783 from matrix-org/rav/sso_login_type
Add 'getSsoLoginUrl' function
2018-11-19 14:13:52 +01:00
Richard van der Hoff 5b51096e37 fix default login type for getSsoLoginUrl 2018-11-19 13:08:41 +00:00
Travis Ralston ac79d6bcee Merge pull request #784 from matrix-org/bwindels/missingheroescrash
Fix: don't set the room name to null when heroes are missing.
2018-11-16 13:23:57 -07:00
David Baker b6e056f832 Merge pull request #785 from matrix-org/dbkr/crypto_db_version_upgrade
Handle crypto db version upgrades
2018-11-16 17:32:40 +00:00
David Baker d99a22d68d Update to new API
Also fix test & remove debug logging from test
2018-11-16 14:46:18 +00:00
David Baker 2602c155d0 Handle crypto db version upgrades 2018-11-16 11:31:08 +00:00
Bruno Windels 80f562643f never return null as a name 2018-11-16 10:38:44 +01:00
Richard van der Hoff 578cb4e268 Add 'getSsoLoginUrl' function 2018-11-15 19:00:37 +00:00
David Baker c53c6a94d7 Update package-lock
so versions are consistent
2018-11-15 16:38:35 +00:00
David Baker 907cf19f05 Merge remote-tracking branch 'origin/develop' into dbkr/e2e_backups 2018-11-15 16:35:52 +00:00
David Baker 88682e1c3b Merge pull request #780 from matrix-org/dbkr/olm_session_unwedge
Restart broken Olm sessions
2018-11-15 16:34:10 +00:00
David Baker 20a4edf899 Merge pull request #776 from matrix-org/dbkr/use_session_last_received_message
Use the last olm session that got a message
2018-11-15 16:33:59 +00:00
David Baker 3222b11346 v0.14.0-rc.1 2018-11-15 15:06:39 +00:00
David Baker fc9d6a6d47 Prepare changelog for v0.14.0-rc.1 2018-11-15 15:06:39 +00:00
David Baker c9917e4079 lint 2018-11-15 10:03:16 +00:00
Bruno Windels b233ab87bb Merge branch 'master' into develop 2018-11-14 18:21:01 +01:00
Bruno Windels 73c3a709de Merge branch 'release-v0.13.1' 2018-11-14 18:19:24 +01:00
Bruno Windels 6ce7b30b72 v0.13.1 2018-11-14 18:18:12 +01:00
Bruno Windels 980d55a2f3 Prepare changelog for v0.13.1 2018-11-14 18:18:11 +01:00
Richard Lewis 988be62804 Add function to get currently joined room members. 2018-11-14 18:15:14 +01:00
Richard Lewis 23efd0850d Add function to get currently joined rooms. 2018-11-14 18:15:08 +01:00
David Baker 17e0f1d9ab Merge branch 'dbkr/use_session_last_received_message' into dbkr/olm_session_unwedge 2018-11-14 14:58:21 +00:00
David Baker 3c85bd55d3 Time goes forwards 2018-11-14 14:57:48 +00:00
David Baker 2298d72ab9 Merge branch 'dbkr/use_session_last_received_message' into dbkr/olm_session_unwedge 2018-11-14 14:35:33 +00:00
David Baker 408407b33d Fix typo 2018-11-14 14:34:36 +00:00
David Baker ab426384e1 Merge branch 'dbkr/use_session_last_received_message' into dbkr/olm_session_unwedge 2018-11-14 14:30:10 +00:00
David Baker 5bc68c0c6d Handle last received message ts being undefined 2018-11-14 14:29:03 +00:00
David Baker ebf20d5b2c Don't force more than one new session per device per hour 2018-11-14 14:20:55 +00:00
David Baker 93d9c40323 Merge branch 'dbkr/use_session_last_received_message' into dbkr/olm_session_unwedge 2018-11-14 14:20:42 +00:00
Bruno Windels c6ea976d7f Merge branch 'master' into develop 2018-11-14 10:55:13 +01:00
Bruno Windels 5f24915300 Merge branch 'release-v0.13.0' 2018-11-14 10:54:52 +01:00
Bruno Windels fbe174fb64 v0.13.0 2018-11-14 10:53:42 +01:00
Richard Lewis 977d5331c0 Update CHANGELOG. 2018-11-14 10:50:05 +01:00
Richard Lewis d40d7e18f5 Update CHANGELOG. 2018-11-14 10:49:54 +01:00
Richard Lewis 11be68ad49 Refactor code to base 'login' method. 2018-11-14 10:49:44 +01:00
Richard Lewis b0d0782a72 Linting. 2018-11-14 10:49:36 +01:00
Richard Lewis dbb6d8ac71 Set access_token and user_id after login in with username and password. 2018-11-14 10:49:25 +01:00
David Baker a30845f9ce lint 2018-11-14 08:03:23 +00:00
David Baker 379f290b8b Add package-lock.json
to force base-x to version 3.0.4 because 3.0.5 breaks the build
by exporting ES6.
2018-11-13 14:49:56 +00:00
David Baker 6c413bba48 Typo + exit if session was never shared 2018-11-13 12:15:33 +00:00
David Baker e17a39d446 PR feedback 2018-11-13 12:10:26 +00:00
David Baker fcadf6ec4a Store last received message ts on olm session 2018-11-12 18:12:43 +00:00
David Baker 231fde219c Store last received message ts on olm session 2018-11-12 18:10:11 +00:00
David Baker 2774bd238b Merge pull request #781 from matrix-org/travis/block-send-if-failed
Prevent messages from being sent if other messages have failed to send
2018-11-09 10:54:38 +00:00
Travis Ralston fed67192bc Fix test: Actually resend the event after unknown devices are found
This test didn't actually test that resending would work, despite its name.
2018-11-08 17:17:30 -07:00
Travis Ralston 16db970558 Appease the linter 2018-11-08 16:51:21 -07:00
Travis Ralston c9a79bf32e Prevent messages from being sent if other messages have failed to send
Fixes https://github.com/vector-im/riot-web/issues/5408
2018-11-08 16:46:03 -07:00
David Baker d74ed508f9 Restart broken Olm sessions
* Start a new Olm sessions with a device when we get an undecryptable
   message on it.
 * Send a dummy message on that sessions such that the other end knows
   about it.
 * Re-send any outstanding keyshare requests for that device.

Also includes a unit test for megolm that isn't very related but came
out as a result anyway.

Includes https://github.com/matrix-org/matrix-js-sdk/pull/776
Fixes https://github.com/vector-im/riot-web/issues/3822
2018-11-08 19:09:28 +00:00
David Baker eafba9c7ef Merge pull request #777 from matrix-org/dbkr/olm_unit_test
A unit test for olm
2018-11-06 12:04:55 +00:00
Richard Lewis 610923af89 Merge pull request #778 from matrix-org/rxl881/loginWithPassword
Set access_token and user_id after login in with username and password.
2018-11-05 17:25:31 +00:00
Richard Lewis 23dfeb13df Update CHANGELOG. 2018-11-05 17:17:22 +00:00
Richard Lewis f4abd7d027 Update CHANGELOG. 2018-11-05 17:06:39 +00:00
Richard Lewis b716e71784 Refactor code to base 'login' method. 2018-11-05 16:40:11 +00:00
Richard Lewis 094598196a Linting. 2018-11-05 16:02:30 +00:00
David Baker db1d1c49a0 Merge pull request #779 from matrix-org/rxl881/getRooms
Add function to get currently joined rooms.
2018-11-05 12:14:12 +00:00
David Baker ff4125c11e Remove unused stuff & comments 2018-11-05 09:39:46 +00:00
Richard Lewis a0d51803ed Add function to get currently joined room members. 2018-11-05 00:08:04 +00:00
Richard Lewis 3aabd63975 Add function to get currently joined rooms. 2018-11-04 21:49:17 +00:00
Richard Lewis 394e37f9ea Set access_token and user_id after login in with username and password. 2018-11-04 19:43:18 +00:00
Travis Ralston 369b88d6f8 Merge pull request #775 from matrix-org/travis/cleanup-request
Remove the request-only stuff we don't need anymore
2018-11-02 09:39:23 -06:00
David Baker ec8b3ae515 Lint (and also name the test right) 2018-11-02 12:33:46 +00:00
David Baker c94382b46c A unit test for olm
Megolm has plenty but none for just the olm layer by itself. I
was playing with getting session re-establishment to work and a
unit test came out.
2018-11-02 12:05:13 +00:00
David Baker 2a6a67c6cc Inbound session creation counts as a received message 2018-11-01 17:08:43 +00:00
David Baker 37f0a9ad7b Try tests on node 10 2018-11-01 13:54:41 +00:00
David Baker 28540ad50a Use the last olm session that got a message
Implements https://github.com/matrix-org/matrix-doc/pull/1596
For https://github.com/vector-im/riot-web/issues/3822
Requires https://github.com/matrix-org/olm-backup/pull/77 (+release)
2018-11-01 12:59:38 +00:00
David Baker 29d92d3e81 Lint 2018-10-31 20:05:21 +00:00
David Baker 0477f354c9 Fix key forwarded count
It's exported in snake case
2018-10-31 19:45:29 +00:00
David Baker c7a0c1402c refer to getAllEndToEndInboundGroupSessions for magic numbers 2018-10-31 19:39:07 +00:00
David Baker 2af5643243 Clarify comment 2018-10-31 19:37:19 +00:00
David Baker 5e9885946f random double linebreak 2018-10-31 19:36:30 +00:00
Travis Ralston e89879d8a6 Remove the request-only stuff we don't need anymore
This was introduced in https://github.com/matrix-org/matrix-react-sdk/pull/2250 but can be pulled out due to https://github.com/matrix-org/matrix-js-sdk/pull/770. See https://github.com/vector-im/riot-web/issues/7634 for more information about the future.
2018-10-31 13:07:31 -06:00
David Baker 2f219f83db Catch exceptions from backupGroupSession() 2018-10-31 18:46:02 +00:00
David Baker 63e9f794c7 Remove unnecessary if 2018-10-31 18:37:38 +00:00
David Baker 7c0b910d7a remove unnecessary isFinite check 2018-10-31 18:36:55 +00:00
David Baker c77ecad9a5 clarify comment 2018-10-31 18:34:49 +00:00
David Baker db2897cf1e Remove spurious interlopers 2018-10-31 18:33:31 +00:00
David Baker 5c5ce0dfe3 Typo 2018-10-31 18:32:48 +00:00
David Baker 6de213483c Change getDeviceByIdentityKey() to just the 2 arg version 2018-10-31 18:31:56 +00:00
David Baker f5846b89ea More modern loop syntax 2018-10-31 18:18:41 +00:00
David Baker c5e7bedb37 Conclusion: no, it shouldn't 2018-10-31 18:08:53 +00:00
David Baker 2b46c560c7 Add crypto. prefix to keyBackupStatus event 2018-10-31 18:07:12 +00:00
David Baker c6ad0665b5 factor out duplicated test code 2018-10-31 18:03:40 +00:00
David Baker 8ab84dee16 PR feedback 1/n 2018-10-31 17:40:17 +00:00
Travis Ralston 62b2c07be2 Merge pull request #770 from matrix-org/travis/fix-request
Manually construct query strings for browser-request instances
2018-10-30 15:18:33 -06:00
Travis Ralston 2fb29ae8fd Use the right query string lib 2018-10-30 14:59:57 -06:00
Travis Ralston b57e858ad1 We can't use arrow functions, apparently. 2018-10-30 14:27:51 -06:00
Travis Ralston 054aac17aa Just use the interface provided to us rather than hack in a flag 2018-10-30 14:25:25 -06:00
Travis Ralston 68b65dd357 Because uglify-js breaks everything 2018-10-30 14:12:46 -06:00
Travis Ralston f2881126cd Manually construct query strings for browser-request instances
Because `request` just doesn't work for us in the browser, but `browser-request` is fine despite us having to do our own query strings.

Fixes https://github.com/vector-im/riot-web/issues/7620
2018-10-30 13:59:29 -06:00
Bruno Windels 11968a5888 Merge pull request #769 from matrix-org/bwindels/initcryptoll
Fix: correctly check for crypto being present
2018-10-30 16:13:32 +00:00
Bruno Windels ad279dc566 correctly check for crypto being present 2018-10-30 16:05:44 +01:00
David Baker 2814932845 lint 2018-10-30 12:36:03 +00:00
David Baker a2430dbc53 Fix DeviceList index of users by identity key
Was causing all keys to be send as unverified
2018-10-30 12:29:44 +00:00
David Baker e51d2dd36a Fix a few e2e backup bits
* Don't _maybeSendKeyBackup() as soon as we enable them: we shouldn't
   have anything to send anyway until we mark all sessions for backup,
   which we do just afterwards, so leave that to trigger the upload
   (otherwise the uploading triggered by backupAll just returns
   straight away because a backup is already in progress).
 * Pass delay & retry params to _maybeSendKeyBackup(): we want the
   all-key upload to happen straight away so pass in delay=0, and
   we also don't want to retry on a timer if the the user is waiting.
 * If we fail due to an HTTP 400 or similar, don't swallow the error.
 * Use the right indexeddb store
2018-10-30 11:45:19 +00:00
David Baker 604af1ac8c Merge branch 'master' into develop 2018-10-29 14:01:26 +00:00
David Baker 68c6393eb2 v0.12.1 2018-10-29 13:59:39 +00:00
David Baker 4cbf9c7f47 Prepare changelog for v0.12.1 2018-10-29 13:59:39 +00:00
Travis Ralston 8bb3b75b1d Merge pull request #768 from aaronraimist/lint
Update babel-eslint to 8.1.1
2018-10-26 21:13:04 -06:00
Aaron Raimist a76f0c7cb4 Update babel-eslint to 8.1.1
Signed-off-by: Aaron Raimist <aaron@raim.ist>
2018-10-26 22:07:40 -05:00
Travis Ralston 01e31afcbd Merge pull request #764 from matrix-org/travis/permalink-routing
Support `request` in the browser and support supplying servers to try in joinRoom()
2018-10-26 14:22:51 -06:00
David Baker 3b2f2f922e Bump db version 2018-10-26 18:49:05 +01:00
Travis Ralston 64b83b3245 Merge branch 'develop' into travis/permalink-routing 2018-10-26 09:20:54 -06:00
David Baker 563e6b3cdd Fix jsdoc 2018-10-26 13:23:37 +01:00
David Baker 6518bff2ac Merge remote-tracking branch 'origin/develop' into dbkr/e2e_backups 2018-10-26 12:09:38 +01:00
David Baker 0e26247b53 Speed up time rather than increasing timeouts 2018-10-26 12:08:55 +01:00
David Baker e69f7dbc5f Merge pull request #767 from matrix-org/dbkr/loglevel_dep
loglevel should be a normal dependency
2018-10-26 11:26:15 +01:00
David Baker 4d0f6df89a alphabetical order 2018-10-26 11:23:05 +01:00
David Baker 6b184363a1 loglevel should be a normal dependency
rather than a dev dependency
2018-10-26 11:16:20 +01:00
David Baker b519069634 Merge pull request #766 from matrix-org/dbkr/stop_devicelist
Stop devicelist when client is stopped
2018-10-26 11:14:36 +01:00
Travis Ralston 1bd44a7427 Merge remote-tracking branch 'origin/develop' into travis/permalink-routing 2018-10-25 14:36:46 -06:00
Travis Ralston 568ff5a3f5 Appease the linter 2018-10-25 14:36:14 -06:00
David Baker a6bf40d4e2 We can always import these now 2018-10-25 19:21:29 +01:00
David Baker b3bb99d76a Stop client after backup tests 2018-10-25 19:11:43 +01:00
David Baker 243bab7036 Merge branch 'dbkr/stop_devicelist' into dbkr/e2e_backups 2018-10-25 19:03:57 +01:00
David Baker 88b39f4b67 Stop devicelist when client is stopped
To avoid the devicelist trying to save after the client has been
stopped

Hopefully will fix random test failures on node 11.
2018-10-25 19:00:03 +01:00
David Baker 5e8061f846 Merge remote-tracking branch 'origin/develop' into dbkr/e2e_backups 2018-10-25 17:33:48 +01:00
David Baker 870e96a1df Merge pull request #743 from matrix-org/dbkr/wasm
Update to WebAssembly-powered Olm
2018-10-25 15:58:18 +01:00
David Baker 59070c2af6 Merge remote-tracking branch 'origin/develop' into dbkr/wasm 2018-10-25 15:29:41 +01:00
David Baker cec8936728 Merge pull request #763 from Ryuno-Ki/logging-lib
Logging lib. Fixes #332
2018-10-25 15:28:47 +01:00
David Baker 14071b0d31 Merge pull request #765 from matrix-org/dbkr/mock_request_stop
Use new stop() method on matrix-mock-request
2018-10-25 15:16:08 +01:00
David Baker 57173e4385 Use mock-request 1.2.2 2018-10-25 15:01:05 +01:00
David Baker 997caad985 jsdoc 2018-10-25 14:43:17 +01:00
David Baker 2b752c0c02 Use new stop() method on matrix-mock-request
To finish all pending flushes between tests. This stops the unit
tests from hanging on node 11 when run in certain combinations.

Requires https://github.com/matrix-org/matrix-mock-request/pull/6
(so will need a release of matrix-mock-request before merging)
2018-10-25 14:29:25 +01:00
Travis Ralston 2cccb8b450 Install memfs because webpack is made of fail 2018-10-24 16:57:36 -06:00
Travis Ralston 0c540ac8de Re-add the querystring options 2018-10-24 16:36:12 -06:00
Travis Ralston 6033b7b886 Update request and browserify; Use request in the browser 2018-10-24 16:36:00 -06:00
Travis Ralston b67f8d1389 Merge branch 'develop' into travis/permalink-routing 2018-10-24 16:22:18 -06:00
André Jaenisch ae645ad9f0 Use Node.js module export, since ES6 export breaks build.
Signed-off-by: André Jaenisch <andre.jaenisch@posteo.de>
2018-10-24 21:52:50 +02:00
André Jaenisch 5b72509dac Fix broken build.
Signed-off-by: André Jaenisch <andre.jaenisch@posteo.de>
2018-10-24 21:12:51 +02:00
André Jaenisch 3ce42a096b Add Apache license banner.
Signed-off-by: André Jaenisch <andre.jaenisch@posteo.de>
2018-10-24 21:06:04 +02:00
André Jaenisch 8331c2f267 Use ES6 export instead of CommonJS.
Signed-off-by: André Jaenisch <andre.jaenisch@posteo.de>
2018-10-24 21:05:21 +02:00
André Jaenisch b3c9570b0f Remove 'use strict'.
Signed-off-by: André Jaenisch <andre.jaenisch@posteo.de>
2018-10-24 20:56:29 +02:00
André Jaenisch 9d5c877df9 Set loglevel to DEBUG to remain current behaviour.
Signed-off-by: André Jaenisch <andre.jaenisch@posteo.de>
2018-10-24 20:55:54 +02:00
André Jaenisch a8e2727473 Set level of logger instead of all of them.
Signed-off-by: André Jaenisch <andre.jaenisch@posteo.de>
2018-10-24 20:55:22 +02:00
David Baker 4b9c6e6bd2 Merge remote-tracking branch 'origin/develop' into dbkr/wasm 2018-10-24 19:15:04 +01:00
David Baker d29ac088c0 retest 2018-10-24 18:55:04 +01:00
David Baker 3a316de9ef Update to Olm 3 here too 2018-10-24 17:40:58 +01:00
David Baker 40cb37e824 Update to Olm 3 2018-10-24 17:37:33 +01:00
David Baker f165b55a1d Merge branch 'e2e_backups' of git://github.com/uhoreg/matrix-js-sdk into uhoreg-e2e_backups 2018-10-24 17:15:36 +01:00
David Baker 84b91d4575 Update to Olm 3 2018-10-24 16:58:48 +01:00
David Baker f5832423f4 v0.12.1-rc.1 2018-10-24 11:14:59 +01:00
David Baker 73dd07aadf Prepare changelog for v0.12.1-rc.1 2018-10-24 11:14:59 +01:00
André Jaenisch 0f39a45734 Fixing module export of logger. Refs #332
Signed-off-by: André Jaenisch <andre.jaenisch@posteo.de>
2018-10-24 01:44:10 +02:00
André Jaenisch f41060c39a Replace console.log with loglevel logger. Fixes #332
Signed-off-by: André Jaenisch <andre.jaenisch@posteo.de>
2018-10-24 00:48:57 +02:00
André Jaenisch bbb8e12bac Create logger module. Refs #332
Signed-off-by: André Jaenisch <andre.jaenisch@posteo.de>
2018-10-24 00:48:57 +02:00
André Jaenisch d0e1471c91 Added loglevel library. Refs #332
Signed-off-by: André Jaenisch <andre.jaenisch@posteo.de>
2018-10-24 00:48:52 +02:00
Hubert Chathi 322ef1fd63 update backup algorithm name to agree with the proposal 2018-10-22 11:28:16 -04:00
Travis Ralston 47cb97bc60 Merge pull request #762 from aaronraimist/valid-package-json
Add repository type to package.json to make it valid
2018-10-20 21:55:48 -06:00
Aaron Raimist 8d35bea830 Add repository type to package.json to make it valid
Signed-off-by: Aaron Raimist <aaron@raim.ist>
2018-10-20 21:38:04 -05:00
Travis Ralston d8bcc4e3f1 Initial support for specifying which servers to try in joinRoom
This has a bug when using browser-request where the query string for `server_name: [a, b]` comes out as `?server_name=a,b` instead of `?server_name=a&server_name=b`. This is due to browser-request not supporting the same qs options as request, so the qsStringifyOptions do nothing.
2018-10-19 13:34:22 -06:00
Hubert Chathi 434ac86090 properly fill out the is_verified and first_message_index fields 2018-10-19 10:51:19 -04:00
Travis Ralston 1061026f96 Merge pull request #761 from Half-Shot/hs/upload-limits
Add getMediaConfig()
2018-10-16 09:04:08 -06:00
Will Hunt e638c49160 Merge remote-tracking branch 'upstream/develop' into hs/upload-limits 2018-10-16 11:32:21 +01:00
David Baker 5d84db9fb7 Merge branch 'release-v0.12.0' 2018-10-16 10:49:14 +01:00
David Baker 874bdea634 v0.12.0 2018-10-16 10:47:33 +01:00
David Baker 68497d3a1f Prepare changelog for v0.12.0 2018-10-16 10:47:32 +01:00
David Baker b9e198c172 Oops: remove debug logging 2018-10-15 11:39:39 +01:00
Hubert Chathi 40d0a82342 remove accidental change to eslintrc 2018-10-12 15:45:48 -04:00
Hubert Chathi d49c0a1bcb more de-linting and fixing 2018-10-12 14:28:31 -04:00
Hubert Chathi 91fb7b0a7c fix unit tests for backup recovery 2018-10-12 12:03:51 -04:00
Hubert Chathi 9b12c22823 de-lint plus some minor fixes 2018-10-12 10:38:10 -04:00
Hubert Chathi 3957006fae Merge remote-tracking branch 'upstream/dbkr/e2e_backups' into e2e_backups 2018-10-11 14:01:26 -04:00
David Baker 874029dff0 oops - fix changelog format in retrospect 2018-10-11 15:38:34 +01:00
David Baker 6aff3ed407 v0.12.0-rc.1 2018-10-11 14:42:29 +01:00
David Baker c0ae78ae82 Prepare changelog for v0.12.0-rc.1 2018-10-11 14:42:29 +01:00
David Baker 8b22f01ecd Merge pull request #760 from matrix-org/bwindels/fixleavesinmemberlist
never replace /sync'ed memberships with OOB ones
2018-10-11 14:01:38 +01:00
Bruno Windels 2ed694b041 remove supersedes OOB logic 2018-10-11 14:32:03 +02:00
Bruno Windels a0ef6ab811 typo 2018-10-11 14:14:42 +02:00
Bruno Windels d098b39024 never replace /sync'ed memberships with OOB ones 2018-10-11 14:11:40 +02:00
David Baker 3cf23f8a5c Document breaking change 2018-10-11 12:13:25 +01:00
Hubert Chathi fc59bc2992 add localstorage support for key backups 2018-10-10 19:32:07 -04:00
Hubert Chathi da65f43983 wrap backup sending in a try, and add delays 2018-10-10 19:31:28 -04:00
Bruno Windels 72e77d237a Merge pull request #759 from matrix-org/dbkr/lazy_load_check_before_sync
Don't fail to start up if lazy load check fails
2018-10-10 18:28:47 +02:00
David Baker ecc3e18e85 typo 2018-10-10 17:27:06 +01:00
David Baker dea70af889 remove debug logging 2018-10-10 17:06:26 +01:00
David Baker 30362091e5 Don't fail to start up if lazy load check fails
Do the lazy loading check in the batch of things we do before
starting a sync rather than at client start time, so we don't fail
to start the client if we can't hit the HS to determine LL support.

Fixes https://github.com/vector-im/riot-web/issues/7455
2018-10-10 16:59:36 +01:00
David Baker ada4b6ef16 Lint 2018-10-09 15:46:12 +01:00
David Baker 59e6066579 Replace base58check with a simple parity check
base58check seems way overcomplicated for this purpose (plus the
module was exporting an es6 file, breaking the js-sdk build). A
parity check empirically detects single substitution and transposition
errors. Another option would be Luhn's algorithm.
2018-10-09 14:15:03 +01:00
David Baker 0aa3362671 Merge pull request #754 from matrix-org/dbkr/e2e_on_interslice
Make e2e work on Edge
2018-10-09 10:48:24 +01:00
David Baker 5873db7331 Merge remote-tracking branch 'origin/develop' into dbkr/wasm 2018-10-09 10:47:11 +01:00
David Baker b3fe05ec81 Merge remote-tracking branch 'origin/develop' into dbkr/e2e_backups 2018-10-09 10:35:15 +01:00
David Baker 92fbf58b13 Merge pull request #758 from matrix-org/bwindels/betteridberrors
throw error with same name and message over idb worker boundary
2018-10-09 09:59:55 +01:00
Bruno Windels a4b2cc84c7 Merge pull request #755 from matrix-org/travis/fix-vuln-warning
Default to a room version of 1 when there is no room create event
2018-10-08 09:55:10 +02:00
David Baker 89c3f6fa0e Merge remote-tracking branch 'origin/develop' into dbkr/e2e_backups 2018-10-05 14:01:43 +01:00
David Baker 1395da694e Merge pull request #757 from matrix-org/dbkr/gone_away_is_the_bluebird
Silence bluebird warnings
2018-10-05 13:25:16 +01:00
David Baker 264b20535e Silence bluebird warnings 2018-10-05 12:13:05 +01:00
Bruno Windels caba350b33 throw error with same name and message over idb worker boundary
instead of string currently thrown. This allows handling error
from the main thread.
2018-10-05 12:48:12 +02:00
Travis Ralston d9fe194111 Default to a room version of 1 when there is no room create event
Fixes https://github.com/vector-im/riot-web/issues/7331

There is something to be worried about when there is no room create event, however. This should always be available, although due to cache problems or servers that don't provide the event we can't be sure of this.
2018-10-04 13:42:07 -06:00
Hubert Chathi 258adda67c retry key backups when they fail 2018-10-04 15:19:20 -04:00
David Baker 40dc13b2e2 lint try 2 2018-10-04 15:38:08 +01:00
David Baker 4cda54ca1c lint 2018-10-04 15:15:30 +01:00
David Baker 8116c5b3f7 Make e2e work on Edge
We were sucessfully opening indexeddb but any queries using compound
indicies were failing because Edge doesn't support them, so messages
were failing to decrypt with 'DataError'.

Try a dummy query at startup, so if it fails we fall back to a
different store (ie. end up using localstorage on Edge).
2018-10-04 13:49:32 +01:00
David Baker 35d584c67b Remove outdated comment 2018-10-04 13:05:45 +01:00
David Baker 9504cbcc4f Merge remote-tracking branch 'origin/develop' into dbkr/wasm 2018-10-04 11:19:37 +01:00
Bruno Windels 1dcc5127d0 Merge pull request #750 from matrix-org/bwindels/allownonffmerge
allow non-ff merge from release branch into master
2018-10-04 10:22:02 +02:00
Bruno Windels 6790699279 Merge pull request #739 from matrix-org/ben/new-examples
add new examples, to be expanded into a post
2018-10-04 10:21:36 +02:00
David Baker 85e3d7083c Merge pull request #751 from matrix-org/dbkr/indexeddb_errors
Reject with the actual error on indexeddb error
2018-10-03 16:22:26 +01:00
David Baker 262ace1773 commit the recovery key util file 2018-10-03 10:20:57 +01:00
David Baker 7cd101d8cb Fix recovery key format 2018-10-02 19:22:10 +01:00
David Baker ce2058aea9 Merge branch 'dbkr/wasm' into dbkr/e2e_backups 2018-10-02 16:54:36 +01:00
David Baker e9b0acaa8e Merge remote-tracking branch 'origin/develop' into dbkr/e2e_backups 2018-10-02 16:50:37 +01:00
David Baker bd2da08c4e Reject with the actual error on indexeddb error
Rather than the event
2018-10-02 16:48:27 +01:00
Bruno Windels 0a88d419c6 allow non-ff merge from release branch into master 2018-10-01 17:21:01 +02:00
Bruno Windels 80c190db36 Merge branch 'master' into develop 2018-10-01 15:40:16 +02:00
Bruno Windels 550cf00ee7 Merge branch 'release-v0.11.1' 2018-10-01 15:36:37 +02:00
Ben Parsons fbca7951fc improvements suggested by Bruno 2018-10-01 14:29:24 +01:00
Bruno Windels 1e1358fcef v0.11.1 2018-10-01 15:23:37 +02:00
Bruno Windels fd1b3329f5 Prepare changelog for v0.11.1 2018-10-01 15:23:37 +02:00
Bruno Windels 9c9d8468a5 Merge pull request #744 from matrix-org/dbkr/update_mocha
Update mocha to v5
2018-09-28 12:57:49 +01:00
Bruno Windels 55ca03f100 make release compatible with latest release of hub (2.5) 2018-09-28 12:51:31 +02:00
Bruno Windels 83708725b2 check youre logged in with correct npm user when releasing 2018-09-28 11:16:00 +02:00
David Baker 6f59d62e5c Merge pull request #748 from matrix-org/bwindels/nollforguests
disable lazy loading for guests as they cant create filters
2018-09-27 19:29:54 +01:00
Bruno Windels 1c348f0cdb disable lazy loading for guests as they cant create filters 2018-09-27 18:55:21 +01:00
Bruno Windels 634596257d v0.11.1-rc.1 2018-09-27 11:47:28 +01:00
Bruno Windels 5e4973a1dc changelog 2018-09-27 11:45:33 +01:00
Bruno Windels 19f023e0ee Revert "v0.11.1-rc.1"
This reverts commit 090c15fe19.
2018-09-27 11:42:19 +01:00
Bruno Windels 090c15fe19 v0.11.1-rc.1 2018-09-27 11:38:25 +01:00
Bruno Windels e8b307dc4f Prepare changelog for v0.11.1-rc.1 2018-09-27 11:38:25 +01:00
Bruno Windels 056479d450 Revert "v0.11.1-rc.1"
This reverts commit 847d40e567.
2018-09-27 11:35:52 +01:00
Bruno Windels e5ebe2f888 Merge pull request #747 from matrix-org/bwindels/releasehubversion
make usage of hub compatible with latest version (2.5)
2018-09-27 11:30:17 +01:00
Bruno Windels e8e1b431ad make usage of hub compatible with latest version (2.5) 2018-09-27 11:28:01 +01:00
Bruno Windels 847d40e567 v0.11.1-rc.1 2018-09-27 11:20:21 +01:00
Bruno Windels cf6c555e6a Prepare changelog for v0.11.1-rc.1 2018-09-27 11:20:21 +01:00
Bruno Windels b508aa9ebc Merge pull request #746 from matrix-org/bwindels/resynconlltoggle
Detect when lazy loading has been toggled in client.startClient
2018-09-27 09:49:01 +01:00
Bruno Windels 5e7634506e Merge branch 'develop' into bwindels/resynconlltoggle 2018-09-26 18:23:07 +01:00
Bruno Windels ba39b64ced re-enable test 2018-09-26 18:00:40 +01:00
David Baker 33ad65a105 Don't assume Olm will be available from start
By doing `Olm = global.Olm` on script load, we require that Olm is
available right from the start, which isn't really necessary. As
long as it appears some time before we actually want to use it,
this is fine (we can probably assume it's not going to go away
again..?)

This means Riot doesn't need to faff about making sure olm is
loaded before starting anything else.
2018-09-26 16:39:22 +01:00
Travis Ralston fcebe89f6c Merge branch 'hs/upload-limits' into develop 2018-09-26 09:14:12 -06:00
Bruno Windels 2d5eb920b8 pass lazy loading flag into error, to format message based on it 2018-09-26 16:12:30 +01:00
Travis Ralston 26de2c86ed Merge pull request #745 from matrix-org/revert-644-hs/upload-limits
Revert "Add getMediaLimits to client"
2018-09-26 09:12:03 -06:00
Travis Ralston cba1e95d0a Revert "Add getMediaLimits to client" 2018-09-26 09:11:28 -06:00
Bruno Windels 78a5a88638 fix jsdoc lint and better naming 2018-09-26 12:49:33 +01:00
Bruno Windels b7b9c67259 fix lint 2018-09-26 12:49:26 +01:00
Bruno Windels 54bff81470 clear sync data on toggling LL,also throw spec. error and delegate clear
the sync data needs to be cleared toggling in both directions:
 not LL -> LL: you want to get rid of all the excess state in the
               sync data to get the RAM benefits
 LL -> not LL: you want to fill the sync data state again
               because getOutOfBandMembers won't be called
2018-09-26 12:39:56 +01:00
David Baker fe21972f4a Update mocha to v5
Mostly to get the non-vulnerable version of node-growl
2018-09-26 11:36:06 +01:00
Bruno Windels 58e3c72446 only store serializable options (string, boolean, number) 2018-09-26 11:34:58 +01:00
Bruno Windels 6dd5c6c317 fix existing missing this 2018-09-26 11:33:19 +01:00
Bruno Windels 4e0af3eafe don't return the IDBEvent from storeClientOptions
as it's not needed and not cloneable
2018-09-26 11:32:43 +01:00
Bruno Windels 1d0791142c all store methods should return a promise 2018-09-26 11:32:11 +01:00
Bruno Windels 2560ba2980 dont clear the store if its a brand new one 2018-09-26 10:37:52 +01:00
Bruno Windels 19be3dd852 fix lint 2018-09-26 10:13:40 +01:00
Travis Ralston 40f2a6978b Merge pull request #644 from Half-Shot/hs/upload-limits
Add getMediaLimits to client
2018-09-25 18:12:55 -06:00
Bruno Windels 1fd8c43d94 fix tests 2018-09-25 18:50:09 +01:00
David Baker 63cc3fd890 lint 2018-09-25 18:14:11 +01:00
David Baker c556ca40b1 Support Olm with WebAssembly
wasm Olm has a new interface: it now has an init method that needs
to be called and the promise it returns waited on before the Olm
module is used. Support that, and allow Crypto etc to be imported
whether Olm is enabled or not. Change whether olm is enabled to
be async since now it will be unavailable if the async module init
fails. Don't call getOlmVersion() until the Olm.init() is done.
2018-09-25 17:49:54 +01:00
Matthew Hodgson 3e32f47903 Merge pull request #742 from matrix-org/travis/build-process
Split npm start into an init and watch script
2018-09-25 17:31:23 +01:00
Travis Ralston 8f2824186a Split npm start into an init and watch script
This is to better support riot-web's build process without losing the functionality supplied by `npm start`. The watch script no longer performs an initial build and thus `start:init` has been created for this purpose.
2018-09-25 10:09:58 -06:00
Bruno Windels b0dbb20e22 fixup of in memory stores 2018-09-25 15:53:40 +01:00
Bruno Windels 0519c4c6b1 await startClient and use promises also so error gets shown 2018-09-25 15:53:14 +01:00
Bruno Windels 28184b4a29 check if lazy loading was enabled before in startClient 2018-09-25 15:32:10 +01:00
Bruno Windels 76175abea2 allow storing client options in indexeddb
so we can tell what options the sync data was created with
2018-09-25 15:30:35 +01:00
Ben Parsons d28f829b1c add new examples, to be expanded into a post 2018-09-20 13:12:50 +01:00
Bruno Windels 184c3dce15 Merge pull request #738 from matrix-org/revert-733-bwindels/roomnamealias
Revert "room name should only take canonical alias into account"
2018-09-20 12:23:38 +02:00
David Baker a08a3078da Revert "room name should only take canonical alias into account" 2018-09-20 11:20:49 +01:00
David Baker c2100d7622 Merge pull request #737 from matrix-org/bwindels/fixnamedisambiguation
fix display name disambiguation with LL
2018-09-20 10:22:38 +01:00
Bruno Windels a91fa59174 fix display name disambiguation with LL 2018-09-19 18:14:18 +02:00
Bruno Windels 574a6b68ae Merge pull request #735 from matrix-org/bwindels/fixstalerr
Introduce Room.myMembership event
2018-09-19 13:35:11 +02:00
David Baker 2f4c1dfcc4 Test all 3 code paths on backup restore 2018-09-18 17:33:47 +01:00
David Baker 1b62a21dbd Free PkEncryption/Decryption objects 2018-09-18 16:12:37 +01:00
David Baker a78825eff9 Bump to Olm 2.3.0 for PkEncryption 2018-09-18 15:06:28 +01:00
David Baker 0bad7b213e Fix lint
Remove commented code block as it's not immediately obvious it makes
sense or is the right way of suggesting a key restore.
2018-09-18 14:56:11 +01:00
David Baker e4bb37b1a8 Fix lint mostly 2018-09-18 14:53:59 +01:00
David Baker 54c443ac68 Make tests pass 2018-09-18 14:48:02 +01:00
David Baker 3af9af96ea More linting 2018-09-17 19:31:37 +01:00
David Baker f75d188131 Soe progress on linting 2018-09-17 19:25:42 +01:00
Bruno Windels fc3a00054f add test for new event 2018-09-17 19:33:36 +02:00
Bruno Windels 84e41c2ade fix tests 2018-09-17 18:28:07 +02:00
Bruno Windels 4630733b55 don't fall back anymore to member, as this is more reliable 2018-09-17 18:23:48 +02:00
Bruno Windels eb690e14e4 introduce Room.myMembership event
As you don't always have your own member with lazy loading
of members enabled, looking at the sync response section
where a room appears is the most reliable way of determining
the syncing user's membership in a room.

Before we already used this to read the
current room membership with `room.getMyMembership()`,
but we were still using the `RoomMember.membership` event
to detect when the syncing user's membership changed.

This event will help make those checks work well with LL enabled.
2018-09-17 18:20:49 +02:00
David Baker 009430e829 Add isValidRecoveryKey
Add method to check if a given string is a valid recovery key
2018-09-17 17:04:29 +01:00
David Baker 073fb73ff3 Make multi-room key restore work 2018-09-17 15:59:37 +01:00
David Baker e789747834 Check sigs on e2e backup & enable it if we can 2018-09-14 17:06:27 +01:00
David Baker 833002f846 Merge pull request #733 from matrix-org/bwindels/roomnamealias
room name should only take canonical alias into account
2018-09-14 11:38:26 +01:00
David Baker 3838fab788 WIP e2e key backup support
Continues from uhoreg's branch
2018-09-13 17:01:05 +01:00
Bruno Windels 907e9fc476 fix lint 2018-09-13 10:04:31 +02:00
Bruno Windels b829a39cd2 fix tests 2018-09-13 09:59:20 +02:00
Bruno Windels daa7af0605 room name should only take canonical alias into account 2018-09-13 09:52:21 +02:00
David Baker 47a1adc864 Merge pull request #732 from matrix-org/bwindels/fixcontextstatenotwrapped
state events from context response were not wrapped in a MatrixEvent
2018-09-11 13:27:03 +01:00
Bruno Windels 98e448acdd state events from context response were not wrapped in a MatrixEvent 2018-09-11 14:13:35 +02:00
David Baker 72bd51f26e Merge remote-tracking branch 'origin/develop' into uhoreg-e2e_backups 2018-09-11 12:02:47 +01:00
David Baker 29db856322 Merge branch 'e2e_backups' of git://github.com/uhoreg/matrix-js-sdk into uhoreg-e2e_backups 2018-09-11 12:00:46 +01:00
David Baker cd5cda916f Merge branch 'master' into develop 2018-09-10 11:35:25 +01:00
David Baker 33a1139772 v0.11.0 2018-09-10 11:33:51 +01:00
David Baker c5b62903f3 Prepare changelog for v0.11.0 2018-09-10 11:33:50 +01:00
David Baker 387fd16b40 v0.11.0-rc.1 2018-09-07 14:28:45 +01:00
David Baker c91b67d370 Prepare changelog for v0.11.0-rc.1 2018-09-07 14:28:44 +01:00
David Baker b809d1c263 Merge pull request #724 from matrix-org/bwindels/fastermemberinsert
Reduce amount of promises created when inserting members
2018-09-07 13:57:44 +01:00
David Baker 7bb6675abf Merge pull request #726 from matrix-org/bwindels/dontwaitforstorage
dont wait for LL members to be stored to resolve the members
2018-09-07 13:57:20 +01:00
David Baker b91bea94f4 Merge pull request #728 from matrix-org/bwindels/wrongemitargs
RoomState.members emitted with wrong argument order for OOB members
2018-09-07 13:57:09 +01:00
David Baker 96e21700bd Merge pull request #727 from matrix-org/dbkr/conn_did_fail
Only emit CATCHUP if recovering from conn error
2018-09-07 13:31:17 +01:00
David Baker 7e8f25bce3 Include 404 in connectivity success error codes 2018-09-07 13:18:45 +01:00
Bruno Windels 9e02049b05 RoomState.members emitted with wrong argument order for OOB members 2018-09-07 14:11:27 +02:00
David Baker affdfccd60 Only emit CATCHUP if recovering from conn error
Have the keepalive promise return a boolean indicating whether it
detected a connectivity failure or not. Use this to only emit
CATCHUP if there was a connectivity error, to try & suppress
the state flip-flopping back & forth between CATCHUP and ERROR
in the case where we have connectivity but the sync is returning
and error for whatever reason.
2018-09-07 11:29:44 +01:00
David Baker 402f8c27a9 Merge pull request #725 from matrix-org/dbkr/fix_sync_error_doc
Fix docstring for sync data.error
2018-09-07 09:44:28 +01:00
Bruno Windels ba4dc6c60a dont wait for LL members to be stored to resolve the members
this can easily add up to 100ms / 1000 of members
2018-09-07 10:44:06 +02:00
David Baker 6b8dd42547 Fox docstring for sync data.error
It's 'error', not 'err'
2018-09-06 19:53:52 +01:00
Bruno Windels 1511a27f4c update/remove comments 2018-09-06 18:09:46 +02:00
Bruno Windels 7ae6c147fa lint doesnt like async 2018-09-06 18:02:19 +02:00
Bruno Windels f51630eb07 dont create a promise for every inserted member but await the transaction instead 2018-09-06 18:01:20 +02:00
Bruno Windels e3586411e0 Merge pull request #723 from matrix-org/revert-721-revert-717-bwindels/fixllroompermission
Re-apply "Don't rely on members to query if syncing user can post to room"
2018-09-06 14:55:24 +02:00
David Baker a0639a32c7 Revert "Revert "Don't rely on members to query if syncing user can post to room"" 2018-09-06 11:47:40 +01:00
David Baker 759c6e77a7 Merge pull request #721 from matrix-org/revert-717-bwindels/fixllroompermission
Revert "Don't rely on members to query if syncing user can post to room"
2018-09-05 18:09:59 +01:00
David Baker 04ad3d7c3c Revert "Don't rely on members to query if syncing user can post to room" 2018-09-05 18:04:19 +01:00
Bruno Windels 49badd9a2f Merge pull request #717 from matrix-org/bwindels/fixllroompermission
Don't rely on members to query if syncing user can post to room
2018-09-05 13:07:29 +02:00
Bruno Windels 8b00083bca check power levels without relying on membership
as this might not be known for the syncing user.
instead, add a method to room which always knows the syncing user's membership
2018-09-05 12:36:53 +02:00
Bruno Windels 3d98e324b5 Merge pull request #719 from matrix-org/bwindels/fixavatars-parttrois
Fixes for room.guessDMUserId
2018-09-04 18:17:39 +02:00
Bruno Windels 768c66313f remove unneeded async keywords 2018-09-04 18:09:47 +02:00
Bruno Windels 3561fd1c05 Merge pull request #716 from matrix-org/bwindels/fixfilepanel
Fix filepanel also filtering main timeline with LL turned on.
2018-09-04 17:10:49 +02:00
Bruno Windels a6c055b6d1 Merge pull request #711 from matrix-org/bwindels/clearidbmembersonleave
Remove lazy loaded members when leaving room
2018-09-04 16:53:56 +02:00
Bruno Windels 0d24c18fed add comment to explain fix 2018-09-04 16:51:36 +02:00
Bruno Windels dd8b2a79fb Merge pull request #702 from matrix-org/bwindels/fixreconnectspinner
Fix: show spinner again while recovering from connection error
2018-09-04 16:39:38 +02:00
Bruno Windels f0095611bc add new CATCHUP state as breaking change 2018-09-04 16:38:46 +02:00
Bruno Windels a3567f0918 some tests for room.guessDMUserId() 2018-09-04 13:01:45 +02:00
Bruno Windels f0d3d0d74e remove unneeded checks, we should always have enough heroes or members 2018-09-04 13:01:15 +02:00
Bruno Windels 3e32bc0d5d check heroes is not falsy first, this would fail without LL 2018-09-04 13:00:34 +02:00
Bruno Windels 632e4aa120 pick the first member, dont need an array 2018-09-04 13:00:00 +02:00
Bruno Windels 2391ce198d this method returns userId, not member 2018-09-04 12:59:37 +02:00
David Baker e5e2bbd482 Merge branch 'master' into develop 2018-09-03 14:11:12 +01:00
David Baker 0b6632123b v0.10.9 2018-09-03 14:09:24 +01:00
David Baker b1801fc953 Prepare changelog for v0.10.9 2018-09-03 14:09:24 +01:00
Bruno Windels ca1a1c4f28 Merge pull request #714 from matrix-org/bwindels/memberlist-spinner
Add method to query LL state in client
2018-09-03 15:05:13 +02:00
Bruno Windels 3363cc4f1d shallow-clone the filter, so the timeline filter doesnt get written into it later on 2018-09-03 15:02:38 +02:00
Bruno Windels f84684982f add method to query LL state in client 2018-09-03 11:14:23 +02:00
Bruno Windels 3bed5969bf remove count logging, approach confirmed to work and be according to idb spec 2018-09-03 10:27:00 +02:00
Bruno Windels ebc162e3d8 do onLeft (which clears the LL members) as late as possible
to avoid chance that something might call loadMembersIfNeeded
on the room and load them back again.
2018-08-31 16:13:34 +02:00
Bruno Windels f30136dba3 only clear promise at the end to avoid race between load and clear members 2018-08-31 16:12:54 +02:00
Bruno Windels 9b1926f902 also clear out lazy loaded members from storage 2018-08-31 16:11:37 +02:00
David Baker d29524ba3f Merge pull request #707 from matrix-org/bwindels/llinvites
Fix: also load invited members when lazy loading members
2018-08-31 14:09:42 +01:00
Bruno Windels 7258fe4e5c clear out of band members in store when leaving room 2018-08-31 14:42:15 +02:00
Bruno Windels f8ea1702f8 store support for removing out of band members for a room 2018-08-31 14:42:15 +02:00
David Baker 7582c28c1a v0.10.9-rc.2 2018-08-31 13:40:37 +01:00
David Baker 5042eb87e7 Prepare changelog for v0.10.9-rc.2 2018-08-31 13:40:36 +01:00
David Baker e8e80732ef Merge pull request #710 from matrix-org/bwindels/avatarfixbis
Fix for "otherMember.getAvatarUrl is not a function"
2018-08-31 13:32:25 +01:00
David Baker b8744a79ae Merge pull request #708 from matrix-org/bwindels/avatarfixbis
Fix for "otherMember.getAvatarUrl is not a function"
2018-08-31 10:34:29 +01:00
Bruno Windels 414b153d28 also fallback to getting avatar from user 2018-08-31 11:05:06 +02:00
Bruno Windels 8e160dda8e make sure getAvatarFallbackMember always returns a member 2018-08-31 11:03:26 +02:00
David Baker 9b54c9b807 Merge pull request #704 from matrix-org/dbkr/discardsession
Pass through function to discard megolm session
2018-08-30 18:25:50 +01:00
Bruno Windels 1bb608cdb6 remove filter for LL members so invite members are also sent 2018-08-30 17:29:16 +02:00
Bruno Windels 1239485b30 fix test 2018-08-30 15:42:15 +02:00
Bruno Windels 0d23d047fc use CATCHUP state after ERROR before going back to SYNCING 2018-08-30 15:37:05 +02:00
Bruno Windels d837ae64ac triple = 2018-08-30 15:37:05 +02:00
Bruno Windels d72a70396a Pass through PREPARED state after error, when keepalive returns succes.
This is according to the state diagram in client.js.
This will show a spinner at the bottom of a room again
while the catchup sync is in progress,
which seems to have broken at some point.
2018-08-30 15:37:05 +02:00
David Baker 938772b86a v0.10.9-rc.1 2018-08-30 14:16:14 +01:00
David Baker 3e88593a81 Prepare changelog for v0.10.9-rc.1 2018-08-30 14:16:14 +01:00
David Baker 60c01d7869 Revert b0b0291 and a6de395
To make tests pass again
2018-08-30 12:03:53 +01:00
David Baker 1cbcc61bd6 Merge pull request #706 from matrix-org/bwindels/fixdmavatar
Fix DM avatar
2018-08-30 11:43:07 +01:00
Bruno Windels 7f5a2974ce allow self chats 2018-08-30 12:37:13 +02:00
Bruno Windels 3de3ea38b9 check heroes is present 2018-08-30 11:47:17 +02:00
Bruno Windels 3659e86d57 fix lint, actually get members as well 2018-08-30 11:41:35 +02:00
Bruno Windels c335a6b3de guess DM user id, used to patch up incorrect m.direct account data 2018-08-30 11:05:31 +02:00
Bruno Windels 267b831bc4 calculate fallback avatar for rooms with <= members but not DM 2018-08-30 11:05:05 +02:00
Bruno Windels 7ee93cb910 make sure our user is not in the summary heroes.
No reason to think it is right now, but if there is a server bug
we could end up showing your own avatar for a DM again.

Also convenience method as we add up invited + join count often
2018-08-30 11:04:32 +02:00
Matthew Hodgson ae95a49618 spell out m.new_devices no longer exist 2018-08-29 20:20:52 +01:00
David Baker 8f98504183 jsdoc 2018-08-29 18:11:53 +01:00
David Baker 1b77ee0ef4 Pass through function to discard megolm session
To make debugging crypto slightly faster
2018-08-29 18:06:45 +01:00
Matthew Hodgson a6de395cde unbreak tests from b0b0291bc7 2018-08-28 18:03:59 +01:00
Bruno Windels fcd6dd34b2 Merge pull request #699 from matrix-org/bwindels/fixlle2ememberfetch-bis
Lazy loading: avoid loading members at initial sync for e2e rooms
2018-08-28 15:36:42 +02:00
Bruno Windels a6ebfe4215 typo 2018-08-28 15:31:20 +02:00
Bruno Windels 6a9158aa62 Merge pull request #700 from matrix-org/bwindels/fixllmegolmsession
Improve setRoomEncryption guard against multiple m.room.encryption st…
2018-08-28 15:30:16 +02:00
Matthew Hodgson b0b0291bc7 hopefully fix invite_room_state as per https://github.com/vector-im/riot-web/issues/7229 2018-08-28 01:27:20 +01:00
Bruno Windels 85f1da1f10 revert unnecesary changes 2018-08-27 12:09:10 +02:00
Bruno Windels c47445ca98 no need to just add a space now 2018-08-27 12:01:22 +02:00
Bruno Windels 4e25867548 revert to async event processing
without LL, we could refresh the device list before all members have been tracked.
as promises, even resolved ones (in case of no LL), always continue async
2018-08-27 11:56:46 +02:00
Bruno Windels ad71bb30ac add comment back as we kept flag in the end 2018-08-27 11:51:29 +02:00
Bruno Windels 362bf1895d restore inhibitDeviceQuery param to avoid breaking change 2018-08-27 11:12:00 +02:00
Bruno Windels 7d00c0bd5a make LL/non-LL flow in Crypto more alike by always going through _roomDeviceTrackingState 2018-08-27 10:54:08 +02:00
Bruno Windels 5e5994f166 try and fix tests 2018-08-27 10:54:08 +02:00
Bruno Windels 7247762b60 Also support not lazy-loading members in Crypto 2018-08-27 10:54:08 +02:00
Bruno Windels 21e0c79f7d Revert "Revert "Lazy loading: don't block on setting up room crypto""
This reverts commit 5cf2ebea4f.
2018-08-27 10:54:08 +02:00
Bruno Windels 78b08bfef2 fix var declaration 2018-08-27 10:48:11 +02:00
Bruno Windels ae7e90dc2f do config comparison first to keep original error message 2018-08-27 10:39:14 +02:00
Hubert Chathi bf873bde42 split the backup version creation into two different methods 2018-08-24 22:13:13 -04:00
Hubert Chathi 017f81e430 fix some bugs 2018-08-24 16:39:22 -04:00
Bruno Windels 0028bfbfc7 fix lint 2018-08-24 18:42:48 +02:00
Bruno Windels 60c9c403bd Improve setRoomEncryption guard against multiple m.room.encryption state events
we were only bailing out when receiving a non JSON-identical m.room.encryption event.
When receiving an identical event, the algorithm in _roomEncryptors would be reset,
generating a new megolm session every time this happens (there is a LL synapse bug
where this happens on every sync).

As the _roomList is backed by indexeddb you might already have a config without the algorithm being present though,
so we first check for the room encryptor algorithm being present. If so, always bail out as setRoomEncryption was
already called for the given room.

If no algorithm is present, still check if the config is not being changed.
Also setup the roomlist and room encryption synchronously before awaiting
the indexeddb operation to store the room encryption config in roomlist.
2018-08-24 18:28:38 +02:00
David Baker ec5fff2046 Merge branch 'e2e_backups' of git://github.com/uhoreg/matrix-js-sdk into uhoreg-e2e_backups 2018-08-24 13:29:29 +01:00
Bruno Windels a7199a3d0d Merge pull request #698 from matrix-org/revert-696-bwindels/fixlle2ememberfetch
Revert "Lazy loading: don't block on setting up room crypto"
2018-08-23 14:10:13 +02:00
Bruno Windels 5cf2ebea4f Revert "Lazy loading: don't block on setting up room crypto" 2018-08-23 14:05:57 +02:00
David Baker 580e95605e Merge pull request #696 from matrix-org/bwindels/fixlle2ememberfetch
Lazy loading: don't block on setting up room crypto
2018-08-23 12:41:33 +01:00
David Baker 8c3d1df3cf Merge pull request #695 from matrix-org/dbkr/hide_replaced_rooms
Add getVisibleRooms()
2018-08-23 09:34:39 +01:00
David Baker 7c66f91429 Typo 2018-08-23 09:34:23 +01:00
Hubert Chathi 73e294b1bd add copyright header to backup.spec 2018-08-23 00:29:29 -04:00
Hubert Chathi e5ec479923 check that crypto is enabled 2018-08-23 00:27:30 -04:00
Hubert Chathi 75107f99b2 pass in key rather than decryption object to restoreKeyBackups 2018-08-23 00:26:21 -04:00
Hubert Chathi fb8efe368a initial draft of API for working with backup versions 2018-08-23 00:03:36 -04:00
Hubert Chathi 1faf477537 fix formatting and fix authedRequest usage 2018-08-22 23:58:59 -04:00
Bruno Windels e3d108454c fix test 2018-08-22 23:25:37 +02:00
Bruno Windels 806b40727d fix lint 2018-08-22 23:00:27 +02:00
Bruno Windels fa702efe8f fix typo 2018-08-22 19:22:50 +02:00
Bruno Windels 344e3e18ab start tracking room devices in background after finishing loading members 2018-08-22 19:13:18 +02:00
Bruno Windels aea9eaa307 Only start tracking devices in an e2e room when needed
This way we can put off loading the members
2018-08-22 18:00:38 +02:00
Bruno Windels dffe0b39b6 Merge pull request #697 from matrix-org/dbkr/joined_member_count_wrapper
Add wrapper around getJoinedMemberCount()
2018-08-22 17:44:59 +02:00
Bruno Windels 267d660527 add invite count to room as well 2018-08-22 17:42:30 +02:00
David Baker 4c3046f917 Add wrapper around getJoinedMemberCount()
On Room, because it's super confusing that Room has
getJoinedMembers() but not getJoinedMemberCount()

https://github.com/matrix-org/matrix-react-sdk/pull/2126 had assumed
that this method was on Room
2018-08-22 16:34:06 +01:00
Bruno Windels 71444b638b don't block on setting up room crypto
this will load members in case of LL and could take quite some time
The two async actions performed by onCryptoEvent is saving the crypto config
to the roomlist store and fetching the room members to track.
Both are guarded against double calls so not awaiting this should be fine.
2018-08-22 14:45:36 +02:00
David Baker 962ec7bb53 Add getVisibleRooms()
To hide rooms that have been replaced

For https://github.com/vector-im/riot-web/issues/7164
2018-08-22 11:58:44 +01:00
Bruno Windels 52fad6aec2 Merge pull request #694 from matrix-org/hs/fetch-room-event-api
Api to fetch events via /room/.../event/..
2018-08-21 14:39:14 +02:00
Will Hunt 5b830f0b6a Add fetchRoomEvent to base-apis.js 2018-08-21 11:07:48 +01:00
Will Hunt 3d24c8768f Drop fetchRoomEvent from client.js 2018-08-21 11:07:37 +01:00
Bruno Windels 977b8625f8 Merge pull request #693 from matrix-org/dbkr/room_upgrades
Support for room upgrades
2018-08-21 11:22:08 +02:00
Will Hunt df7dc04a1d Happy linter, happy developer 2018-08-20 14:16:47 +01:00
David Baker 269d3cb086 Merge branch 'master' into develop 2018-08-20 13:49:20 +01:00
David Baker 4d310cd461 v0.10.8 2018-08-20 13:44:30 +01:00
David Baker 88c5c39fcb Prepare changelog for v0.10.8 2018-08-20 13:44:29 +01:00
Will Hunt 79ca68300c Add callback 2018-08-20 12:47:26 +01:00
Will Hunt baca20b225 Add support for /rooms/$roomId/event/$eventId 2018-08-20 11:14:45 +01:00
David Baker 0e3cb1977f lint 2018-08-17 15:00:54 +01:00
David Baker 8b1fa72877 Copyright & debadgering 2018-08-17 14:59:34 +01:00
David Baker e8610a35b4 Support for room upgrades
For https://github.com/vector-im/riot-web/issues/7164
2018-08-17 14:55:12 +01:00
Bruno Windels 77e6442f73 Merge pull request #691 from matrix-org/bwindels/feature_lazyloading
Lazy loading of room members
2018-08-16 18:14:33 +02:00
David Baker eeddfd4919 v0.10.8-rc.1 2018-08-16 15:17:36 +01:00
David Baker fa16da86b3 Prepare changelog for v0.10.8-rc.1 2018-08-16 15:17:35 +01:00
Bruno Windels 372a628cab fix log whitespace 2018-08-15 12:01:26 +02:00
Bruno Windels 2f4d8c3530 check with server if it supports member lazy loading 2018-08-15 12:01:26 +02:00
Bruno Windels 482eab0e2a fix tests 2018-08-15 12:01:26 +02:00
Bruno Windels 03c63d9b12 use sync token for /members request, as synapse expects it now 2018-08-15 12:01:26 +02:00
Bruno Windels 07e87915ba fix and add tests 2018-08-15 12:01:26 +02:00
Bruno Windels 91f2bf99c0 fix lint 2018-08-15 12:01:26 +02:00
Bruno Windels 535d59db4d fixup 2018-08-15 12:01:26 +02:00
Bruno Windels 9739c3355a undo postponing tracking device keys on turning on room encryption 2018-08-15 12:01:26 +02:00
Bruno Windels 827db37eef fixup 2018-08-15 12:01:26 +02:00
Bruno Windels 0c6e47a5bc await for LL members in getEncryptionTargetUsers 2018-08-15 12:01:26 +02:00
Bruno Windels 864ea749e5 Move /members fetching to room as getEncryptionTargetMembers needs it 2018-08-15 12:01:26 +02:00
Bruno Windels 5d92ec3b7b prevent deadlock on startup
when loading the encrypted events from storage,
the code would wait for the encryption target users,
which would never come because you would only load them
when viewing the room.

This disabled starting to track the devices in the room
when the inhibitDeviceQuery is set.
2018-08-15 12:01:26 +02:00
Bruno Windels 733a3ed102 Defer encryption targeted users when OOB member loading hasn't started 2018-08-15 12:01:26 +02:00
Bruno Windels b14be026b7 pass LL flag to room, to know if we should wait for lazy members at all 2018-08-15 12:01:26 +02:00
Bruno Windels b4afe97289 spelling 2018-08-15 12:01:26 +02:00
Bruno Windels 43a7a607b2 fixup 2018-08-15 12:01:26 +02:00
Bruno Windels a8bf66d8af Make Room.getEncryptionTargetMembers async, as members might be loading 2018-08-15 12:01:26 +02:00
Bruno Windels 3616a07dbb store /members promise on room while loading members 2018-08-15 12:01:26 +02:00
Bruno Windels 6609dfd410 initial support for lazy loading when calling /context 2018-08-15 12:01:26 +02:00
Bruno Windels e3913bd397 Fix: missed call site while renaming prependStateEvents 2018-08-15 12:01:26 +02:00
Bruno Windels 8c01ed1469 add comments explaining why we ignore the put promise result 2018-08-15 12:01:26 +02:00
Bruno Windels 7aa0dcc89f PR feedback, rename method 2018-08-15 12:01:26 +02:00
Bruno Windels 5285b22a76 bump version as making startClient async is a breaking change 2018-08-15 12:01:26 +02:00
Bruno Windels 0fa49bc2cd PR feedback 2018-08-15 12:00:38 +02:00
Bruno Windels 01d8730850 cleanup, lint and docs 2018-08-15 12:00:38 +02:00
Bruno Windels 52149ce74a Move LL filter creation inside MatrixClient
As we need an option to turn lazy loading on (we can't just accept a filter,
as /messages has an incompatible filter), better only pass the option
and create the filter inside startClient
2018-08-15 12:00:38 +02:00
Bruno Windels bffc20612d Fix: member avatar was always forward looking
applying itself all the way till the next member event
when back paginating
2018-08-15 12:00:38 +02:00
Bruno Windels 2c0eb19a27 Fix sentinels changing from underneath us!
As RoomMember contains the event in a nested object (events.member),
a shallow copy was not enough to be immutable.

This solution won't copy OOB flags but that's not neccesary
for sentinels.
2018-08-15 12:00:38 +02:00
Bruno Windels 748c4737f6 remove dead code 2018-08-15 12:00:38 +02:00
Bruno Windels 769d5113f7 prepend state from /messages to appropriate timeline/room state (excluding event context for now) 2018-08-15 12:00:38 +02:00
Bruno Windels 157be6da05 centralize creating a /messages request
so we only need to add LL filter once
2018-08-15 12:00:38 +02:00
Bruno Windels 1dc4b8bb63 add option for lazy loading to startClient
we need more than just a filter, which is what is passed in now,
so have an explicit option. For now still take the filter but later on
this could be created inside MatrixClient
2018-08-15 12:00:38 +02:00
Bruno Windels f261599435 fix lint 2018-08-15 12:00:38 +02:00
Bruno Windels 2862b49057 Only return hero in getDMInviter if we were invited 2018-08-15 12:00:38 +02:00
Bruno Windels a8d0d8f33d provide method on room to help with DM detection with fallback to summary heroes/counts. 2018-08-15 12:00:38 +02:00
Bruno Windels f55a2079bf replace getMember(myId).membership with getMyMembership
This works with rooms which haven't had their members
loaded yet.
2018-08-15 12:00:38 +02:00
Bruno Windels c0f706a2a2 move userId into room 2018-08-15 12:00:38 +02:00
Bruno Windels b034f67a0f add oob member methods to stub store 2018-08-15 12:00:38 +02:00
Bruno Windels 977b9eb686 implement memory store methods 2018-08-15 12:00:38 +02:00
Bruno Windels 5e11bf735e store OOB status along with members, to avoid unneccesary fetching
for some small rooms, it is possible that calling /members would not
yield any previously unknown members, as they were all recently active.
This would be the case for most DMs.

For these rooms, we'd end up with 0 OOB members after lazy loading them,
so when getting them out of storage we need a way to distuinguist this case
from never having lazy loaded the members of the room at all.

We store a marker object in the same store and return [] or null accordingly.
This way the /members don't get fetched a second time.
2018-08-15 12:00:38 +02:00
Bruno Windels a8c73f7a4d add logging, should be useful as long as not merged into develop 2018-08-15 12:00:38 +02:00
Bruno Windels 86105611fc we dont need a separate index? 2018-08-15 12:00:38 +02:00
Bruno Windels 0364af7337 update indexeddb store to store member events, not profile information 2018-08-15 12:00:38 +02:00
Bruno Windels c618ce4625 store only out of band members 2018-08-15 12:00:38 +02:00
Bruno Windels 2b9c834476 add comment to clarify how we avoid race 2018-08-15 12:00:38 +02:00
Bruno Windels d366ec9c48 prototype how we could store ll members 2018-08-15 12:00:38 +02:00
Bruno Windels ca3981fba8 back-port infinite spinner fix as room state code changed considerably for lazy loading 2018-08-15 12:00:38 +02:00
Bruno Windels bb490faefe fix lint 2018-08-15 11:59:40 +02:00
Bruno Windels d8f673ed51 make sure invited count cache gets reset when updating member 2018-08-15 11:59:40 +02:00
Bruno Windels 6ce7170cf4 counts from summary api should override count members manually as members might not be complete 2018-08-15 11:59:40 +02:00
Bruno Windels 1d71e7243f no need to create new array here 2018-08-15 11:59:40 +02:00
Bruno Windels cf08901d02 fix lint 2018-08-15 11:59:40 +02:00
Bruno Windels 230a9311a0 actually need to subtract one from join+invite count as that includes the syncing user 2018-08-15 11:59:40 +02:00
Bruno Windels 576f7142c1 just need the member names actually 2018-08-15 11:59:40 +02:00
Bruno Windels 20b4285849 add some tests for room name based on room summary + fix because it was actually broken 2018-08-15 11:59:40 +02:00
Bruno Windels d67bdbf088 test accumulating summary fields 2018-08-15 11:59:40 +02:00
Bruno Windels 3a389793ff fix sync accumulator test to include summary 2018-08-15 11:59:40 +02:00
Bruno Windels f5ff5dc3e0 Fix name recalculation tests by not relying on mocking
I tried keeping the mocking but it would take too much
boilerplate code to make the tests work again, and even more
to write the tests for room name with lazy loading.

Just testing everything with a real implementation is not really
a unit test any more, but proved way easier.

It'll be somewhat annoying these tests will fail if there is
something wrong in roomstate (not room), but that's the trade-off
2018-08-15 11:59:40 +02:00
Bruno Windels 00bf5bdf69 unify member a bit access towards getMember
some tests for mock getMember, some for .members
if you use either in the code (as I did for room display name changes)
tests start playing and you play whack-a-mole switching between
both ways of accessing the members in a room.

lets start using one way so mocking becomes easier,
and besides, accessing an object internal members is not the best idea.
2018-08-15 11:59:40 +02:00
Bruno Windels 9541aa7dbf fix lint 2018-08-15 11:59:40 +02:00
Bruno Windels e61c6b89c8 bring room name calculation in line with summary spec, while maintaining some backwards compatibility 2018-08-15 11:59:40 +02:00
Bruno Windels a5b3869e9f add invited count, only copy summary fields if present in summary
only copy any member from summary as
they are only in the response when they change.
Also accumulate them in the sync accumulator
2018-08-15 11:59:40 +02:00
Bruno Windels fbdce27db2 m.heros => m.heroes 2018-08-15 11:59:40 +02:00
Bruno Windels 148876f597 sorting should happen always 2018-08-15 11:59:40 +02:00
Bruno Windels 0cb533beca no need to recalculate name here, as recalculate already does this 2018-08-15 11:59:40 +02:00
Bruno Windels 5811ebd6f3 Support summary heroes in room name calculation
Also clean-up algorithm, and remove assumption
that we have all members as much as possible
2018-08-15 11:59:40 +02:00
Bruno Windels 8fa87f8ba5 make room summary available to Room
from either the sync accumulator or the /sync endpoint
2018-08-15 11:59:40 +02:00
Bruno Windels 21ba4f71f6 jsdoc doesn't like generic promise type annotations 2018-08-15 11:59:40 +02:00
Bruno Windels 097e7df7c9 fix lint 2018-08-15 11:59:40 +02:00
Bruno Windels 83c6615d6e move me || syncedmembership code into room.getmymembership 2018-08-15 11:59:40 +02:00
Bruno Windels f6fafeaafb store membership from during sync, because we might not have own membership
to determine where a room should show up in the room list, we need to know
our membership type. But with lazy loading, we might not have our own member
if we weren't recently active in the room. Using getSyncedMembership can be
used to fallback if the users membership is not yet available.
2018-08-15 11:59:40 +02:00
Bruno Windels 420a88c776 remove obsolete flag 2018-08-15 11:59:40 +02:00
Bruno Windels 5fcf9481b3 fix room not having access to event mapper + tests 2018-08-15 11:59:39 +02:00
Bruno Windels 48c3dcc08a fix lint & fix and add tests 2018-08-15 11:59:39 +02:00
Bruno Windels 62333b3e2c Use /members api for lazy loading
This commit is a substantial change, as /members returns state events,
not profile information as /joined_members, and this allows to simplify
the implementation quite a bit. We can assume again all members have
a state event associated with it.

I also changed most of the naming of lazy loaded members to
out-of-band members to reflect that this is the relevant bit for most
of the code, that the members didn't come through /sync but through
another channel.

This commit also addresses the race condition between /(joined_)members
and /sync. /members returns the members at the point in the timeline
at a given event id. Members are loaded at the last event
in the live timeline, and all members that come in from sync
in the mean time are marked as superseding the out of band members,
so they won't be overwritten, even if the timeline is reset in the
mean time.

Members are also marked if they originate from an out-of-band channel
(/members) so they can be stored accordingly (future PR).

The loading status is kept in room state now, as this made resolving
the race condition easier. One consequence is that the status needs
to be shared across cloned instances of RoomState. When resetting
the timeline (and cloning the room state) while lazy loading is in
progress, one of the RoomStates could be left in progress indefinitely.
Though that is more for clarity than avoiding any actual bugs.
2018-08-15 11:59:39 +02:00
Bruno Windels df758b31b7 fix lint 2018-08-15 11:59:39 +02:00
Bruno Windels 9f08bfaa6f room lazy loading tests + fix 2018-08-15 11:59:39 +02:00
Bruno Windels 198d2c780d test that modifications to clone'd() room state dont affect the old 2018-08-15 11:59:39 +02:00
Bruno Windels ab1c0dabae make sure LL members don't needlessly get disambiguated during a clone
when cloning the state, lazy loaded members are copied over with their rawDisplayName,
which could originate from their userId if they don't have a displayname.
the displayname algorithm would assume that the displayname is explicitly set,
and see if we'd have to disambiguate. As a fix, if the display name is the same as the id, just return the id
2018-08-15 11:59:39 +02:00
Bruno Windels 0234f11914 some tests for room member + state, and some fixes to make them pass 2018-08-15 11:59:39 +02:00
Bruno Windels 79fcc9f343 only set the lazy members on the forward looking state of the live timeline
since back-paginating will also support lazy loading the state needed to display that part of the timeline, and no user interaction
is supposed to happen before the lazy loaded member are, well, loaded, applying the ll members to all timelines should not be neccessary.
2018-08-15 11:59:39 +02:00
Bruno Windels 031f722540 clarify that we only get joined members for now 2018-08-15 11:59:39 +02:00
Bruno Windels 531ccf1819 actually, comment should be 1 line higher 2018-08-15 11:59:39 +02:00
Bruno Windels 0d2ac42dc4 add comment to clarify corners cut in prototype 2018-08-15 11:59:39 +02:00
Bruno Windels 9ec6ea3bdf 2, not 3 times 2018-08-15 11:59:39 +02:00
Bruno Windels 1ce580bba3 test lazy loaded info is returned and then discarded when setting a state event 2018-08-15 11:59:39 +02:00
Bruno Windels 8ad2a94a90 make sure LL member doesn't override state event
extra safety check, as this should already not happen because of the check in RoomState
2018-08-15 11:59:39 +02:00
Bruno Windels de3f75bc83 Lazy loaded members should never take precendence over members acquired through state events 2018-08-15 11:59:39 +02:00
Bruno Windels 008d85ed32 pick joined property out of response 2018-08-15 11:59:39 +02:00
Bruno Windels 5e30aff418 more consistent naming 2018-08-15 11:59:39 +02:00
Bruno Windels 5de0d39553 move the fact that we're prototyping only with joined members up the stack to client
only MatrixClient really needs to know that for now we only load joined members, the rest of the code can be generic for other membership types as that is the eventual plan, to also support invites at least.
2018-08-15 11:59:39 +02:00
Bruno Windels d95d44dc94 move error handling to caller 2018-08-15 11:59:39 +02:00
Bruno Windels 6061deac37 use method for getting state event, less code 2018-08-15 11:59:39 +02:00
Bruno Windels ba34a766e7 fix lint 2018-08-15 11:59:39 +02:00
Bruno Windels 1c81a17298 Fix tests
getSentinelMember now does return a member (with just the userid) when there is no corresponding member yet.
With lazy loading it's perfectly possible the member is not available, and null breaks continuation in the timeline.
2018-08-15 11:59:39 +02:00
Bruno Windels 2097b31d4f handle failed /joined_members call by logging to console and reverting flag 2018-08-15 11:59:39 +02:00
Bruno Windels 2155dd0552 improve return type comment 2018-08-15 11:59:39 +02:00
Bruno Windels 8733654094 remove left-over test code, oops 2018-08-15 11:59:39 +02:00
Bruno Windels abd15748ce fix one lint warning too many 2018-08-15 11:59:39 +02:00
Bruno Windels 9a796f1383 fix lint errors 2018-08-15 11:59:39 +02:00
Bruno Windels 88f2f62945 make resetting the live timeline work with lazily loaded members
In order for the lazy loading logic not to bleed into all corners
of the JS SDK, I moved some of the state copying between timelines
over to the RoomState and EventTimeLine class.
2018-08-15 11:59:39 +02:00
Bruno Windels 30adefed07 return sentinels with userid if members haven't been loaded yet,
better than braking timeline continuation
2018-08-15 11:59:39 +02:00
Bruno Windels 20a1828fa5 make sentinels lazy loading compatible
dont just rely on member events, but just copy the member
2018-08-15 11:59:39 +02:00
Bruno Windels 809674ca2b set lazily loaded members on all RoomStates of a room
for all timelines in all timeline sets
2018-08-15 11:59:39 +02:00
Bruno Windels 0ca3475878 make method to get mxc avatar url public
so MemberInfo can use it and take lazy loading into account
2018-08-15 11:59:39 +02:00
Bruno Windels 32b741e205 use more consistent naming 2018-08-15 11:59:39 +02:00
Bruno Windels c917c4a468 return correct invite sender in case of a join 2018-08-15 11:59:39 +02:00
Bruno Windels 6c584d2b4c keep is_direct checks inside RoomMember as events.member might not be available 2018-08-15 11:59:39 +02:00
Bruno Windels 759d415d40 preserve member state event if available when lazy loading members 2018-08-15 11:59:39 +02:00
Bruno Windels 45d86fa270 emit individual events for lazily loaded members
emit individual RoomState.members/newMember events
for each lazily loaded member as batch events are not a thing.
This makes updating the memberlist work
2018-08-15 11:59:39 +02:00
Bruno Windels 2c5cad71ee prototype support for lazily loading members in matrixclient 2018-08-15 11:59:39 +02:00
Bruno Windels 2b5925b893 Support for updating members in RoomState from lazily loaded members 2018-08-15 11:59:39 +02:00
Bruno Windels f012ada2c4 add setter on RoomMember to update from lazily loaded member 2018-08-15 11:59:07 +02:00
Bruno Windels af1b26ae95 Merge pull request #689 from matrix-org/dbkr/show_room_version
Add getVersion to Room
2018-08-14 17:54:09 +02:00
David Baker f72f5b43e1 Add getVersion to Room
To get the version number of the room as per https://github.com/matrix-org/matrix-doc/issues/1425
2018-08-14 14:27:08 +01:00
Hubert Chathi d55618921b initial implementation of e2e key backup and restore 2018-08-07 23:10:55 -04:00
Bruno Windels c7e1e07262 Merge pull request #680 from matrix-org/dbkr/getsyncstatedata
Add getSyncStateData()
2018-08-06 10:51:21 +02:00
Bruno Windels 24a1bec23d Merge pull request #679 from matrix-org/dbkr/chairman_mau_pt_3_sync_error
Send sync error to listener
2018-08-03 19:06:17 +02:00
David Baker 89ad104423 Add getSyncStateData()
To get additional information about the sync state (ie. the error
object).
2018-08-03 18:00:52 +01:00
David Baker c2f3324302 Send sync error to listener
We do this in other places, but not here
2018-08-03 16:59:10 +01:00
Michael Telatynski 04a969b997 Merge pull request #675 from matrix-org/bwindels/nocrashoninvalidtags
make sure room.tags is always a valid object to avoid crashes
2018-08-01 23:03:51 +01:00
Bruno Windels 630dfa9499 make sure room.tags is always a valid object so no crashes happen later on 2018-07-31 15:33:56 +02:00
Bruno Windels 95668950c2 Merge pull request #673 from matrix-org/bwindels/infinite_spinner
Fix infinite spinner upon joining a room
2018-07-31 14:57:04 +02:00
Bruno Windels 3012501e4b update docs to clarify state when emitting newMember 2018-07-30 14:19:45 +02:00
David Baker 0e81dfb004 v0.10.7 2018-07-30 11:40:08 +01:00
David Baker 35b7f358b6 Prepare changelog for v0.10.7 2018-07-30 11:40:08 +01:00
Bruno Windels e3e48944e0 add test 2018-07-27 11:49:55 +02:00
Bruno Windels 94bbba72f5 add member to members before emitting any events 2018-07-27 11:35:19 +02:00
Michael Telatynski b34716f7e9 take into account homoglyphs when calculating similar display names
to prevent homoglyph attacks

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2018-07-26 14:55:35 +01:00
David Baker c429ca67b9 v0.10.7-rc.1 2018-07-24 18:15:14 +01:00
David Baker bce2ba0785 Prepare changelog for v0.10.7-rc.1 2018-07-24 18:15:13 +01:00
Matthew Hodgson 2613690064 Merge pull request #666 from matrix-org/matthew/encrypt-for-invited-users
encrypt for invited users if history visibility allows.
2018-07-10 16:14:45 +01:00
David Baker 7283076bc8 Fix Users / Members mixup 2018-07-09 17:16:23 +01:00
David Baker f43d05b54e Merge branch 'master' into develop 2018-07-09 13:07:40 +01:00
David Baker c6b500bc09 v0.10.6 2018-07-09 13:06:07 +01:00
David Baker c3972015c7 Prepare changelog for v0.10.6 2018-07-09 13:06:06 +01:00
David Baker 3c18c57857 v0.10.6-rc.1 2018-07-06 15:40:55 +01:00
David Baker f562a06707 Prepare changelog for v0.10.6-rc.1 2018-07-06 15:40:54 +01:00
Michael Telatynski 7f50dd205f displayname disambiguation fixes (#662)
* fix displayname=undefined being disambiguated and strip Zero Width chars
* also strip diaritics and whitespace

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2018-07-06 10:42:53 +01:00
David Baker c4fe15400c Merge pull request #665 from matrix-org/luke/feature-expose-decryption-error-2
Expose event decryption error via Event.decrypted event
2018-07-05 11:02:26 +01:00
David Baker 6e3e8f7310 Merge pull request #663 from matrix-org/luke/feature-decryption-error-codes
Add decryption error codes to base.DecryptionError
2018-07-05 11:00:33 +01:00
Matthew Hodgson 5ae2c26130 lint 2018-07-05 02:01:10 +02:00
Matthew Hodgson d8d35f4022 encrypt for invited users if history visibility allows.
fixes https://github.com/vector-im/riot-web/issues/2713
2018-07-05 01:45:45 +02:00
Luke Barnard fadb4d9219 Send OLM_UNKNOWN_MESSAGE_INDEX when possible 2018-07-04 15:58:45 +01:00
Luke Barnard b63149b36a Fix error code for Olm group message decryption 2018-07-04 14:33:02 +01:00
Luke Barnard 79f92abcfa Add jsdoc for Event.decrypted error 2018-07-04 14:28:15 +01:00
Luke Barnard 0137fb468b Expose event decryption error via Event.decrypted event 2018-07-04 13:56:03 +01:00
Luke Barnard 70ef8760cc Remove spurious console.dir 2018-07-04 11:56:02 +01:00
Luke Barnard c74d2d831b Add decryption error codes to base.DecryptionError
These should roughly follow https://github.com/matrix-org/matrix-ios-sdk/blob/9732cf593206a10d2b60cd01151a759c88c1e9a2/MatrixSDK/Crypto/Algorithms/MXDecryptionResult.h#L21-L39
2018-07-04 11:54:06 +01:00
David Baker 0415f821eb v0.10.5 2018-06-29 11:32:23 +01:00
David Baker 779fe35255 Prepare changelog for v0.10.5 2018-06-29 11:32:22 +01:00
Will Hunt aec7ef6f9c getMediaLimits -> getMediaConfig 2018-06-23 14:38:39 +01:00
Will Hunt 329f09ce0a Media/limits => /config 2018-06-23 14:38:39 +01:00
Will Hunt 68c23af5ae Remove extra return 2018-06-23 14:38:39 +01:00
Will Hunt a54f30c02f Add getMediaLimits to client 2018-06-23 14:38:39 +01:00
Will Hunt fde00b1c62 getMediaLimits -> getMediaConfig 2018-06-23 12:57:29 +01:00
David Baker 11382d2cd7 v0.10.5-rc.1 2018-06-21 10:01:05 +01:00
David Baker ef31131a5d Prepare changelog for v0.10.5-rc.1 2018-06-21 10:01:05 +01:00
Will Hunt 8dd425f8ff Media/limits => /config 2018-06-20 17:24:45 +01:00
David Baker 76feabe32b Merge pull request #659 from matrix-org/t3chguy/media_fixes
fix auth header and filename=undefined
2018-06-18 13:46:59 +01:00
Michael Telatynski 7fe3e2f90a invert argument to make it positive without breaking backwards compat
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2018-06-18 12:27:25 +01:00
Michael Telatynski c0b2151929 allow omitting filename from upload entirely
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2018-06-15 18:25:20 +01:00
Michael Telatynski 5e3b1bf6b0 use Authorization header in media/v1/upload if enabled, instead of query
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2018-06-15 09:57:30 +01:00
Michael Telatynski fdf4523c2a export the Group model class (#656)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2018-06-14 13:52:13 +01:00
David Baker c6cf76f345 Merge branch 'master' into develop 2018-06-12 14:13:15 +01:00
David Baker f16e544691 v0.10.4 2018-06-12 14:11:07 +01:00
David Baker df101217fc Prepare changelog for v0.10.4 2018-06-12 14:11:07 +01:00
David Baker 5d90dc16cc Merge pull request #650 from matrix-org/t3chguy/audio_output
allow setting the output device for webrtc calls
2018-06-08 15:57:25 +01:00
David Baker bbfc1a1cd6 Merge pull request #596 from t3chguy/t3chguy/fix-docs
arguments true and false are actually invalid
2018-06-08 15:21:23 +01:00
Michael Telatynski b7208c12ac Merge pull request #643 from matrix-org/t3chguy/content-type-bugfix
fix typo where `headers` was not being used and thus sent wrong content-type
2018-06-08 12:29:35 +01:00
Michael Telatynski eaa2fdec44 Merge pull request #642 from matrix-org/t3chguy/doc_fixes
fix some documentation typos
2018-06-08 12:17:48 +01:00
David Baker 7099adfe79 v0.10.4-rc.1 2018-06-06 15:40:02 +01:00
David Baker cb17a2bcb0 Prepare changelog for v0.10.4-rc.1 2018-06-06 15:40:01 +01:00
Michael Telatynski 172044a1cb check whether notif level is undefined, because 0 is falsey (#651)
* check whether notif level is undefined, because `0` is falsey and it failed

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* improve number check for all m.room.power_levels related stuffs

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2018-05-29 12:11:29 +01:00
Matthew Hodgson 69204d4fb3 Merge branch 'develop' into matthew/e2e_backups 2018-05-28 00:44:49 +01:00
Michael Telatynski 4b203b6b63 allow setting the output device for webrtc calls
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2018-05-26 16:44:27 +01:00
David Baker 67876bab4c v0.10.3 2018-05-25 15:09:24 +01:00
David Baker 6ce691e40f Prepare changelog for v0.10.3 2018-05-25 15:09:23 +01:00
David Baker 9cd44b09f9 v0.10.3-rc.1 2018-05-24 18:16:59 +01:00
David Baker 73a2704126 Prepare changelog for v0.10.3-rc.1 2018-05-24 18:16:58 +01:00
Luke Barnard a50dd785b8 (Breaking Change) Add erase option to deactivateAccount (#649)
For erasing messages etc. after deactivation.

**Breaking change: `deactivateAccount` no longer takes callback**

Also: Move /account/deactivate from PREFIX_UNSTABLE to _R0
2018-05-24 10:47:41 +01:00
Luke Barnard bafbe5cbec Emit no_consent when M_CONSENT_NOT_GIVEN received (#647) 2018-05-22 18:02:21 +01:00
Leon 9cdcbf6bf8 emit oldEventId on "updatePendingEvent"
It should fire `Room.localEchoUpdated` event and returns the oldEventId
2018-05-22 11:27:05 +08:00
Will Hunt 9596087959 Remove extra return 2018-05-03 17:57:57 +01:00
Will Hunt 6570402b95 Add getMediaLimits to client 2018-05-03 13:43:37 +01:00
David Baker 4153845346 v0.10.2 2018-04-30 13:29:26 +01:00
David Baker 548713ed98 Prepare changelog for v0.10.2 2018-04-30 13:29:25 +01:00
Michael Telatynski 1bf1ce7070 fix typo where headers was not being used and thus sent wrong mime
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2018-04-30 06:56:43 +01:00
David Baker 21dc0fbf2f v0.10.2-rc.1 2018-04-25 14:57:08 +01:00
David Baker 7b29de9698 Prepare changelog for v0.10.2-rc.1 2018-04-25 14:57:08 +01:00
Michael Telatynski 7d468ee148 Replies (#607) (and Content Helpers)
* if event has `m.relates_to` extract this into the e2e-wrapper event

* Split out helpers to make content for {HTML,text}{emote,notice,message}
So that additional content fields can be added before sending

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2018-04-23 14:26:40 +01:00
Michael Telatynski 260e7b529f fix some documentation typos
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2018-04-15 23:31:33 +01:00
Michael Telatynski b027089fc1 use constants 2018-04-15 22:16:25 +01:00
David Baker 8f318528f8 Merge branch 'master' into develop 2018-04-12 12:17:45 +01:00
David Baker 9696e70817 v0.10.1 2018-04-12 12:06:25 +01:00
David Baker 85580acbff Prepare changelog for v0.10.1 2018-04-12 12:06:24 +01:00
David Baker c45a5f7e84 Name param slightly more helpfully 2018-04-12 12:00:41 +01:00
David Baker df74b0b038 Ignore inserts of dup inbound group sessions, pt 2
Stop the error from propagating to the txn error handler and
aborting the rest of the transaction.
2018-04-12 12:00:32 +01:00
David Baker 749e5b8458 Use the name of the error, not the request 2018-04-12 12:00:23 +01:00
David Baker 1255566324 Error message formatting 2018-04-12 12:00:11 +01:00
David Baker a25da4ebf4 Ignore inserts of duplicate inbound group sessions
Rather than aborting the whole txn

This is causing e2e migration fails in the wild
2018-04-12 11:59:59 +01:00
David Baker 72835f3c5f lint 2018-04-12 11:59:46 +01:00
David Baker c4f52f6256 Fix error logging 2018-04-12 11:59:32 +01:00
David Baker 3eb9505633 Log IDB errors 2018-04-12 11:58:33 +01:00
David Baker 543a0be1e4 Merge pull request #641 from matrix-org/dbkr/ignore_duplicate_inbound_group_session_2
Ignore inserts of dup inbound group sessions, pt 2
2018-04-12 11:25:54 +01:00
David Baker 5ef21c5075 Name param slightly more helpfully 2018-04-12 11:20:45 +01:00
David Baker 5c412cc0bd Ignore inserts of dup inbound group sessions, pt 2
Stop the error from propagating to the txn error handler and
aborting the rest of the transaction.
2018-04-12 11:15:39 +01:00
David Baker 346deb4b0d Use the name of the error, not the request 2018-04-11 18:57:52 +01:00
David Baker 6f8f30b018 Error message formatting 2018-04-11 18:56:17 +01:00
David Baker b808757012 Merge pull request #639 from matrix-org/dbkr/ignore_duplicate_inbound_group_session
Ignore inserts of duplicate inbound group sessions
2018-04-11 18:31:43 +01:00
David Baker 5e491bb89a Ignore inserts of duplicate inbound group sessions
Rather than aborting the whole txn

This is causing e2e migration fails in the wild
2018-04-11 17:27:25 +01:00
David Baker fc3cb0dd45 lint 2018-04-11 16:09:28 +01:00
David Baker 0846cb04af Fix error logging 2018-04-11 16:02:56 +01:00
David Baker 9708394846 Merge pull request #638 from matrix-org/dbkr/idb_log_error
Log IDB errors
2018-04-11 15:42:14 +01:00
David Baker 92e4396602 Log IDB errors 2018-04-11 15:37:28 +01:00
Luke Barnard a5870b73a9 Merge branch 'master' into develop 2018-04-11 11:39:26 +01:00
Luke Barnard 77ff6b9088 v0.10.0 2018-04-11 11:38:08 +01:00
Luke Barnard 425e74f297 Prepare changelog for v0.10.0 2018-04-11 11:38:08 +01:00
Luke Barnard ffa184464e v0.10.0-rc.2 2018-04-09 15:29:09 +01:00
Luke Barnard eca68e9e7f Prepare changelog for v0.10.0-rc.2 2018-04-09 15:29:09 +01:00
David Baker dd1bc8ec9a Update to new join policy API (#636) 2018-04-09 14:58:13 +01:00
Luke Barnard 24dc1d6991 Add wrapped to set group is_joinable (#634)
which is exposed in the summary API under `summary.profile.is_joinable`.

If true, anyone can join the group. If false, an invite is required.
2018-04-09 14:58:06 +01:00
Luke Barnard 9f510b7eee Add wrapper for group join API (#633)
Needed for new "joinable" groups.
2018-04-09 14:57:57 +01:00
David Baker 730ca9b60b Update to new join policy API (#636) 2018-04-05 17:02:43 +01:00
Luke Barnard 1afaf903f9 Add wrapped to set group is_joinable (#634)
which is exposed in the summary API under `summary.profile.is_joinable`.

If true, anyone can join the group. If false, an invite is required.
2018-03-29 17:03:20 +01:00
Luke Barnard d53cd41aa6 Add wrapper for group join API (#633)
Needed for new "joinable" groups.
2018-03-29 12:42:16 +01:00
David Baker 3e5ea745d2 Merge pull request #632 from matrix-org/dbkr/remove_ignore_call_log
Remove not very useful but veryv spammy log line
2018-03-27 18:28:08 +01:00
David Baker c7052f7dc7 Remove not very useful but veryv spammy log line 2018-03-27 18:21:03 +01:00
Richard Lewis 56b01df85b Merge pull request #628 from matrix-org/rxl881/stickerEvent
Switch event type to m.sticker.
2018-03-22 10:16:40 +00:00
David Baker aa18eeb7d6 v0.10.0-rc.1 2018-03-19 12:08:11 +00:00
David Baker c18264c615 Prepare changelog for v0.10.0-rc.1 2018-03-19 12:08:11 +00:00
David Baker 11746290a9 Merge pull request #630 from matrix-org/dbkr/fix_repeat_state_events_on_unpeek
Fix duplicated state events in timeline from peek
2018-03-16 15:44:38 +00:00
David Baker 16c062c069 Start first incremental sync request early (#629)
* Start first incremental sync request early

So it can run while we process our sync data.
2018-03-16 15:22:06 +00:00
David Baker 64396de0dc Fix duplicated state events in timeline from peek
When joining a room we were peeking into, we duplicated all the
state events into the timeline. Put back the old behaviour of just
setting them as state events, with copious commentary on how wrong
this seems.
2018-03-15 17:35:18 +00:00
Richard Lewis 8ff78c5d60 Switch event type to m.sticker. 2018-03-12 13:56:50 +00:00
David Baker 349297e495 Merge pull request #627 from matrix-org/dbkr/indexeddb_worker_on_start
Create indexeddb worker when starting the store
2018-03-09 17:43:15 +00:00
David Baker dc3ffb3b30 Fix jsdoc 2018-03-09 17:39:02 +00:00
David Baker 5c2dfb138a Hopefully clarify _startPromise 2018-03-09 17:35:03 +00:00
David Baker 0be679de42 Make local variable look less like a global 2018-03-09 17:31:08 +00:00
David Baker 8bd68e0f10 Create indexeddb worker when starting the store
Rather than when creating it, otherwise we could potentially end
up starting workers unnecessarily.
2018-03-09 16:53:27 +00:00
David Baker df70e16b4d Merge pull request #626 from matrix-org/dbkr/fix_indexeddb_logging
Fix indexeddb logging
2018-03-09 10:29:53 +00:00
David Baker b246545da5 Merge remote-tracking branch 'origin/develop' into dbkr/fix_indexeddb_logging 2018-03-09 10:21:00 +00:00
David Baker 3280cb648f Merge pull request #625 from matrix-org/dbkr/stop_spinner_of_doom
Don't do /keys/changes on incremental sync
2018-03-09 10:19:26 +00:00
David Baker 8798bf42e6 Fix indexeddb logging
1. Fix double 'loaded' on sync data logging
2. Move the 'loaded' message into the bit where the data has
   actually loaded rather than the promise try block.
3. Add '...' to the 'loading' messages so they're easier to tell
   apart from the 'loaded' messages.
2018-03-09 10:16:32 +00:00
David Baker 5a23927e56 Move comment up 2018-03-09 10:09:36 +00:00
Matthew Hodgson beafd597dd ensure indexeddb workers are never double-connected 2018-03-09 02:18:19 +00:00
Matthew Hodgson fbc43b0d58 stupid typo 2018-03-09 00:01:14 +00:00
David Baker facfcf679d DeviceList: bring save forward if necessary
If save is called with a delay that would want the save to happen
sooner then the save we currently have scheduled, cancel the
current save and schedule a new one for the sooner time.
2018-03-08 15:35:35 +00:00
Luke Barnard 68b230a78f Add function to cancel and resend key request (#624) 2018-03-08 15:01:01 +00:00
David Baker 3d1fcc6f83 One day I'll learn to spell guaranteed 2018-03-08 14:26:48 +00:00
David Baker a0578efeb9 fix tests 2018-03-08 13:52:48 +00:00
David Baker 727ad5755e lint 2018-03-08 12:40:01 +00:00
David Baker 4f17352858 Don't do /keys/changes on incremental sync
Remove the call to /keys/changes when we do an incremental syn
where the old sync token doesn't match the one in the device list
store. To allow us to do this, always save the device list store
before saving the sync data, so we can safely assume the device
list store is at least as fresh as the sync token in the sync store.

Thread save functions through to allow this, add a delay parameter
so the sync can save the device list immediately and skip the wait,
and add a wantsSave() method so the sync can skip saving the device
list if the sync store isn't going to save anyway.

Fixes https://github.com/vector-im/riot-web/issues/6068
2018-03-08 12:33:08 +00:00
David Baker 0eb72122ce Merge pull request #623 from matrix-org/dbkr/devicelist_is_too_dirty
Don't mark devicelist dirty unnecessarily
2018-03-07 17:55:38 +00:00
David Baker 66e2b3bb70 Don't mark devicelist dirty unnecessarily
This was marking the device list as dirty even when nothing had
actually changed, causing unnecessary saves.
2018-03-07 17:32:51 +00:00
David Baker 5f12d858eb Keep a push processor and re-use it. (#622)
Because it does some nice caching stuff but that's no good if we
re-create a new one each time.
2018-03-06 19:01:30 +00:00
Luke Barnard e258d6ca8d Fix bug in crypto-store-backend; use oncomplete (#620)
instead of onsuccess for the txn to add an outgoing room key
request.
2018-03-02 12:01:21 +00:00
David Baker 2d25dedbcc Merge pull request #619 from matrix-org/dbkr/cache_joined_member_count
Cache the joined member count for a room state
2018-02-26 16:56:26 +00:00
David Baker bdf6fcb222 Fix tests 2018-02-26 16:53:17 +00:00
David Baker 7e1cea1ef6 Cache the joined member count for a room state
Pushrule evaluation needs the count of joined room members and
filtering the list for joined members takes a nontrivial amount
of time, but caching it is trivial, both code and memory wise.
2018-02-26 16:43:26 +00:00
Richard Lewis d98c803b54 Merge pull request #618 from matrix-org/rxl881/stickers
Fix JS doc
2018-02-23 15:52:42 +00:00
Matthew Hodgson 95238466b5 Merge pull request #617 from matrix-org/dbkr/fix_push_actions_frozen_matrixevents
Precompute push actions for state events
2018-02-23 14:32:11 +00:00
Matthew Hodgson 71652043a0 improve doc 2018-02-23 14:31:51 +00:00
David Baker c9cbaf254b Precompute push actions for state events
State events get froze now, so we can't write to the push actions
cache after having done so: precompute the push actions to work
around this.
2018-02-23 11:06:22 +00:00
David Baker f9cc5cbd33 Merge pull request #616 from matrix-org/luke/fix-unverified-device-blacklist
Fix bug where global "Never send to unverified..." is ignored
2018-02-23 10:11:59 +00:00
Luke Barnard bcb9405793 Instead of emitting, rely on the promise 2018-02-22 18:41:18 +00:00
Luke Barnard 30cb6f196f Fix overriding undefined per-room unverified devices setting
If the per-room setting for preventing sending keys to unverified
devices is `undefined`, it overrides the global setting (which
could be `true`).
2018-02-22 18:02:39 +00:00
Luke Barnard 856ef01632 Emit a crypto.initComplete once crypto is initialised
So that the app can call crypto-dependent functions at
the correct point in time.
2018-02-22 17:59:37 +00:00
David Baker 6f95554655 Merge pull request #615 from matrix-org/dbkr/intern_legacy_membership
Intern legacy top-level 'membership' field
2018-02-22 17:01:38 +00:00
David Baker a72f915646 Intern legacy top-level 'membership' field
There is a legacy top-level 'membership' field on events. We were
interning the normal one in 'content', but not this one, so the
legacy field was still keeping a copy of the string 'join' / 'leave'
etc in memory for every member event.

This make the interning code know about this field.
2018-02-22 16:04:35 +00:00
Matthew Hodgson 94605417f6 Merge pull request #598 from matrix-org/t3chguy/implicit_rr_redaction
Don't synthesize RR for m.room.redaction as causes the RR to go missing.
2018-02-20 18:23:06 +00:00
David Baker 1e017df128 Merge pull request #613 from matrix-org/dbkr/dates_on_demand
Make Events create Dates on demand
2018-02-20 18:08:45 +00:00
David Baker ec27bb5131 Merge pull request #612 from matrix-org/dbkr/dont_clone_events
Stop cloning events when adding to state
2018-02-20 18:08:35 +00:00
David Baker 9637fc098a comment 2018-02-20 18:01:01 +00:00
David Baker 874020ced7 Make Events create Dates on demand
My test account had 37MB (shallow) of Date objects knocking around
in memory. This gets rid of them. I can't see any appreciable
difference in the time taken to switch rooms (where now we recreate
a bunch of Dates that previously would have been cached).
2018-02-20 17:49:25 +00:00
David Baker a7beedcfb6 Unused imports 2018-02-20 17:11:40 +00:00
David Baker 4351c4dd6f Merge pull request #611 from matrix-org/dbkr/use_initialisestate
De-dup code: use the initialiseState function
2018-02-20 17:10:11 +00:00
David Baker 1ddf7fb96c Add XXX comment 2018-02-20 17:07:14 +00:00
David Baker ec5cfe4ee9 Stop cloning events when adding to state
As comment hopefully explains.

On my test account:
Before: 394657 MatrixEvents, 53MB shallow size
After: 198863 MatrixEvents, 27MB shallow size
2018-02-20 16:45:20 +00:00
Matthew Hodgson 4fed2ea7bf log event IDs of ignored calls 2018-02-20 12:38:12 +00:00
Matthew Hodgson ae14cf4740 typo 2018-02-20 12:38:12 +00:00
David Baker 8aa68b3dc1 lint 2018-02-20 11:09:32 +00:00
David Baker e810ee7750 Add test for fixed state misbehaviour 2018-02-20 11:06:33 +00:00
David Baker 9a08194597 Factor out calls to getLiveTimeline 2018-02-19 16:17:30 +00:00
David Baker c77277b60c rename variable 2018-02-19 16:07:28 +00:00
David Baker 21a324558f Comments 2018-02-19 16:05:43 +00:00
David Baker 7a31751564 Merge pull request #610 from matrix-org/dbkr/sentinels_on_demand
Create sentinel members on-demand
2018-02-16 19:01:48 +00:00
David Baker b11bacc2e2 Process state events before timeline events
Don't ignore them: its valid to send them in a non-limited sync,
they're state events that preceed the timeline events.
2018-02-16 18:52:56 +00:00
David Baker 8c02e7ba67 comment 2018-02-16 17:52:39 +00:00
David Baker 275eb8d434 De-dup code: use the initialiseState function
This should behave identically, but the code here appeared to be
identical to the code in initialiseState, so let's use it (it also
has an extra sanity check in there that we only init empty timelines).
2018-02-16 17:49:29 +00:00
David Baker 736d0df38d Handle null userID
The tests inject messages with no sender, so for now let's maintain
behaviour as it was before.
2018-02-16 14:58:19 +00:00
David Baker 89d5d41015 Merge pull request #609 from matrix-org/dbkr/doc_sentinels
Some more doc on how sentinels work
2018-02-16 12:34:34 +00:00
David Baker 0e1444c84b grammar 2018-02-16 11:57:22 +00:00
David Baker 104f8b093d Remove redundant forEach 2018-02-16 11:55:45 +00:00
David Baker 1e638c376b Create sentinel members on-demand
We only need sentinel members for things like the 'sender' field
of events, so we previously created sentinels for everyone in the
room, but a large number of them were never used.

Instead, create them on-demand and cache them.
2018-02-16 11:53:24 +00:00
David Baker a2e1a6ca8f jsdoc doesn't like that 2018-02-16 10:59:44 +00:00
David Baker 337331ff1b Some more doc on how sentinels work 2018-02-16 10:37:50 +00:00
Richard Lewis 1dfde7cd80 Fix JS doc 2018-02-08 11:17:55 +00:00
Luke Barnard 7df2bfe7bc Honour cached push rules (#606)
so that we have push rules loaded into the client after doing
a cached sync (so that the client can assume that we do have
push rules, even if it was loaded from a cached sync).

These rules will be updated once `getPushRules` is completed
prior to the first networked sync.
2018-02-08 09:35:44 +00:00
lukebarnard 4ec90a4b99 Fix incorrect self - should be this 2018-02-07 17:31:16 +00:00
Luke Barnard d4e8f9039c Set sync token before incrementally syncing (#604)
Block on syncing from cache so that the sync token
has been set prior to incremental syncing.
2018-02-07 17:16:22 +00:00
Luke Barnard 4c6c00f16d Fix NPE when loading sync from cache (#603)
* Fix NPE when loading sync from cache
2018-02-07 16:04:47 +00:00
Luke Barnard fd30b25596 Allow a mode of operation without HS connection (#601)
* Allow a mode of operation without HS connection

Instead of blocking the first sync on getting the push rules and
filter, load sync data from disc.

When the client comes online, the push rules will be acquired and
a sync cycle started. This could be immediate if the client is
already online.

This could be a breaking change for clients that get push rules
before a successful sync has been done.
2018-02-06 17:46:39 +00:00
David Baker 032b7cab5d Fix name of 1:1s other person has left (#602)
Use their name as we did before rather than 'Empty Room'

Fixes https://github.com/vector-im/riot-web/issues/6083
2018-02-06 15:43:53 +00:00
David Baker 47dfb4b8cd Merge pull request #597 from matrix-org/dbkr/e2e_rooms_indexeddb
Migrate room encryption store to crypto store
2018-02-06 10:29:29 +00:00
David Baker 51e782b671 Merge pull request #600 from matrix-org/t3chguy/fix_3pid_dm_rooms
add parameter to getIdentityServerUrl to strip the protocol for invites
2018-02-05 12:07:31 +00:00
David Baker cf195262bf inline fucntion that was only used once 2018-01-31 18:12:46 +00:00
David Baker cf72052e46 Correct return type 2018-01-31 17:51:48 +00:00
David Baker 6f50c39b2a more doc 2018-01-31 17:49:58 +00:00
David Baker b6cd826dd7 basically I just can't type 2018-01-31 17:33:35 +00:00
David Baker f1194b1fbe more comment grammar 2018-01-31 17:33:02 +00:00
David Baker c0ca85fb3a comment grammar 2018-01-31 17:32:12 +00:00
David Baker 022df1b143 Revert npm helpfully adding packages 2018-01-31 17:30:12 +00:00
David Baker fdf987f081 Merge remote-tracking branch 'origin/develop' into dbkr/e2e_rooms_indexeddb 2018-01-29 13:40:14 +01:00
David Baker f1e874cd18 Merge pull request #594 from matrix-org/dbkr/device_tracking_indexeddb
Move Device Tracking Data to Crypto Store
2018-01-29 13:38:50 +01:00
David Baker c3bede58aa Fux lying comment 2018-01-29 13:35:03 +01:00
Matthew Hodgson 38915eb7fc fix NPE biting yannick due to broken rules 2018-01-25 18:41:36 +01:00
David Baker 470bd23b3b Add new devices as we get them 2018-01-25 16:49:30 +01:00
Michael Telatynski d007eefe2e add parameter to getIdentityServerUrl to strip the protocol for invites
use new getIdentityServerUrl param in inviteByThreePid
2018-01-25 09:53:19 +00:00
Michael Telatynski 362f442a98 Don't synthesize RR for m.room.redaction as causes the RR to go missing. 2018-01-25 00:04:24 +00:00
David Baker 8c2645c5dd Comment typo 2018-01-24 21:39:46 +01:00
David Baker 1ba0e4809e Implement correct function
in localstorage and memory stores
2018-01-22 18:46:13 +01:00
David Baker 341371b613 lint 2018-01-22 18:37:02 +01:00
David Baker d856285271 Doc param 2018-01-22 18:35:29 +01:00
David Baker e4ffc93463 Remove unused function 2018-01-22 18:34:34 +01:00
David Baker 3149958319 Missed arg 2018-01-22 18:31:17 +01:00
David Baker ac659e8df1 remove debugging 2018-01-22 18:16:11 +01:00
David Baker 81d54c7558 comment typo 2018-01-22 18:11:37 +01:00
David Baker e4de333d83 Use the right prefix function to remove prefix 2018-01-22 17:55:40 +01:00
David Baker e72096328a Actually working migration 2018-01-22 17:46:13 +01:00
David Baker 88a082a533 Hopefully working migration 2018-01-22 17:34:09 +01:00
David Baker 4fbf4f1069 Hopefully address PR feedback 2018-01-22 17:08:19 +01:00
David Baker c360dd11ed Migrate e2e rooms to crypto store
Doesn't do data migration yet
2018-01-22 15:48:53 +01:00
Michael Telatynski 95d582ccee arguments true and false are actually invalid 2018-01-19 23:00:34 +00:00
Matthew Hodgson e0c9b990e7 blindly move crypto.suggestKeyRestore over to /sync 2018-01-18 20:59:08 +00:00
David Baker 074cfb7c58 Add more comments 2018-01-18 17:32:47 +00:00
David Baker 8b649cec8d All the copyrights 2018-01-18 11:52:27 +00:00
David Baker ea6974fc89 Return null if device data isn't set
This is more normal, and the code doesn't expect to get empty
objects here which is reasonable since it never sets one.
2018-01-17 19:27:22 +00:00
David Baker fb29da4e40 Update the catchingup flag
It may change whilst processing the sync
2018-01-17 19:17:48 +00:00
David Baker 461acbcc81 More test fixing
* Change test for new storage layer
 * Always store device keys we download, even if we weren't
   tracking the user.
2018-01-17 18:49:33 +00:00
David Baker 5bab8647b6 Fix device tracking with initial /sync
* Check whether we share an e2e room with user IDs in the 'left'
   field of /keys/changes: there's no guarantee we no longer share
   any e2e rooms with these users
 * Reset everyone's tracking status on an initial sync - just
   re-fetching device lists for all users we're currently tracking
   isn't good enough since room memberships may have changed.
 * Fix typo in test
2018-01-17 16:03:46 +00:00
David Baker f2d1222de7 Merge pull request #591 from matrix-org/luke/perf-push-processor
Optimise pushprocessor
2018-01-17 11:27:50 +00:00
David Baker 585ea14a23 more lint 2018-01-17 09:58:56 +00:00
David Baker 5a0997ded5 lint 2018-01-17 09:52:01 +00:00
David Baker 0174c5674f Make tests pass, finally.
Mostly making tests aware of new storage format or making them
force it to be written. Also some bugfixes like we didn't json
encode some things in the localstorage store and we didn't
correctly check the promise when requesting device data saves.
2018-01-16 17:57:49 +00:00
lukebarnard 9de8653936 Actually check the cache 2018-01-16 09:51:30 +00:00
David Baker 27d28b8247 More test fixing
Allow localstorage store to take a localstorage impl, make TestClient
pass a cryptostore & fix True/true typo
2018-01-15 16:27:28 +00:00
David Baker 110f43a246 Make DeviceList test pass
Includes making saveIfDirty() return a promise in case you care
about when changes got saved (which the test does).
2018-01-15 15:21:39 +00:00
lukebarnard 56612751f9 Handle underride and override rules with patterns 2018-01-15 15:16:22 +00:00
lukebarnard 751fe7349a Fix case insensitivity on new RegExps 2018-01-15 14:54:51 +00:00
Matthew Hodgson fb1b554b86 initial pseudocode WIP for e2e online backups 2018-01-15 01:50:24 +00:00
David Baker 36d7d33afc Null fix & lint
Don't end up with devices / device tracking status being null
2018-01-12 16:44:51 +00:00
David Baker a94f3c720e Fix migration 2018-01-12 15:12:57 +00:00
David Baker 6c1087e429 Migrate from session store 2018-01-12 14:41:08 +00:00
David Baker 2cdb010cff Fix lint 2018-01-12 14:17:10 +00:00
David Baker 60052f59a0 Add localstorage & memory device data stores 2018-01-12 14:05:34 +00:00
David Baker 29d44f809e delay saving so we can batch multiple operations 2018-01-12 12:01:00 +00:00
David Baker 83e4aa2755 Initial attempt at device tracking -> indexeddb
* Message sending works again, but

 * Marking multiple devices known (ie. 'send anyway') is very slow
   because it writes all device info out each time
 * Support for non-indexedb stores not written yet
 * No migration
2018-01-11 18:10:19 +00:00
David Baker eeb97f5b66 Merge pull request #592 from matrix-org/dbkr/set_event_error
Set event error before emitting
2018-01-09 18:08:12 +00:00
David Baker b6f26ae6a5 Oops: new year, new company 2018-01-09 18:05:02 +00:00
Richard Lewis f40435654a Merge pull request #590 from matrix-org/rxl881/stickers
Add event type for stickers [WIP]
2018-01-09 14:49:05 +00:00
David Baker 67d471ea3d Also happy new year and all that 2018-01-09 13:24:29 +00:00
David Baker 4946c5e687 Set event error before emitting
So the event object properties are in a consistent state when we
the event is emitted.

Fixes first part of https://github.com/vector-im/riot-web/issues/5936
2018-01-09 13:23:04 +00:00
David Baker af4f05c29e Merge pull request #587 from matrix-org/dbkr/inbound_sessions_to_cryptostore
Migrate inbound sessions to cryptostore
2018-01-09 10:41:38 +00:00
David Baker 145c76095a Remove unused countEndToEndInboundGroupSessions 2018-01-09 10:37:44 +00:00
Richard Lewis 90045b6faa Fix filtering. 2018-01-08 11:18:36 +00:00
Richard Lewis aef27d811a Set sticker type on event instead of message type. 2018-01-06 00:13:24 +00:00
lukebarnard e2e5f80298 Linting 2018-01-05 20:05:12 +00:00
lukebarnard ca0ed50172 Optimise pushprocessor
by not [re]creating RegExps unnecessarily.
2018-01-05 19:56:27 +00:00
Richard Lewis d44d63c1d6 Send sticker message in to a room. 2018-01-04 21:55:20 +00:00
David Baker 8403042297 Merge pull request #588 from pafcu/displaynames
Disambiguate names if they contain an mxid
2018-01-02 17:46:04 +00:00
David Baker 1568bb014d Merge remote-tracking branch 'origin/develop' into dbkr/inbound_sessions_to_cryptostore 2018-01-02 17:12:47 +00:00
David Baker 7a069c4018 doc return 2018-01-02 17:03:57 +00:00
David Baker c856eb931f Make error message more truthful 2018-01-02 16:32:33 +00:00
David Baker b290ce795f Update comment 2018-01-02 16:21:12 +00:00
David Baker 6f3d279165 Fix doc 2018-01-02 13:43:43 +00:00
David Baker 124ab30f98 jsdoc 2018-01-02 13:42:16 +00:00
David Baker fee90bab66 Wording fix 2018-01-02 13:35:52 +00:00
David Baker e26ade0e62 Wording fix 2018-01-02 13:32:51 +00:00
David Baker c43ccb860b Always migrate inbound group sessions 2018-01-02 13:31:57 +00:00
Johannes Bornhold 81de2b3afc Add getThirdpartyUser
Signed-off-by: Johannes Bornhold <johannes@bornhold.name>
2017-12-31 22:46:18 +01:00
Johannes Bornhold 9a53fa3876 Fix typo around getThirdpartyLocation 2017-12-31 01:24:27 +01:00
Richard van der Hoff f2b7e8b038 Remove spurious linty blank line 2017-12-14 22:36:53 +00:00
Stefan Parviainen 1863f1311b Disambiguate display name if it contains a mxid
Fixes https://github.com/vector-im/riot-web/issues/1811

Signed-off-by: Stefan Parviainen <pafcu@iki.fi>
2017-12-14 21:49:13 +01:00
David Baker e5086f22d6 Merge remote-tracking branch 'origin/develop' into dbkr/inbound_sessions_to_cryptostore 2017-12-08 19:06:36 +00:00
David Baker da90a3ca78 Merge pull request #585 from matrix-org/dbkr/count_sessions_before_migrate
Check for sessions in indexeddb before migrating
2017-12-08 18:29:50 +00:00
David Baker d397c5a251 Merge pull request #586 from matrix-org/dbkr/crypto_store_migrate_warning
Emit an event for crypto store migration
2017-12-08 18:17:23 +00:00
David Baker d26b4434b8 Merge pull request #575 from matrix-org/dbkr/udd_no_auto_show
Supporting fixes For making UnknownDeviceDialog not pop up automatically
2017-12-08 16:33:18 +00:00
David Baker 7188f17f9a more linting 2017-12-07 17:14:11 +00:00
David Baker b0365f8b0e json encode before saving to localstorage 2017-12-07 17:07:06 +00:00
David Baker a1ddeea00e Fix inbound group session migration
Apparently they are parsed at a different layer
2017-12-07 16:50:18 +00:00
David Baker 1414c24caf Missed txn 2017-12-07 15:03:02 +00:00
David Baker 0748c864cd Migrate inbound sessions from session store 2017-12-07 14:57:18 +00:00
David Baker f80626a0fa lint 2017-12-07 14:32:23 +00:00
David Baker 0fffd64a7f Fix key export 2017-12-07 14:23:36 +00:00
David Baker 8da48211d9 Fix exportInboundGroupSession 2017-12-06 22:42:19 +00:00
David Baker 0dd8ffa3a0 Much tedious linting 2017-12-06 22:36:24 +00:00
David Baker 0362e61f36 Unused var 2017-12-06 22:24:57 +00:00
David Baker d92a77f695 lint 2017-12-06 22:24:19 +00:00
David Baker e6dd573e8a Fix test (needs a cryptostore now) 2017-12-06 19:42:01 +00:00
David Baker b6330c3a4f er, this isn't an object 2017-12-06 19:41:44 +00:00
David Baker 10bc714f5c lint 2017-12-06 16:04:00 +00:00
David Baker 8f57723b88 Emit an event for crypto store migration
As we may want to warn the user to not go back to an older version.
2017-12-06 15:24:29 +00:00
David Baker 3d71bee85e Check for sessions in indexeddb before migrating
Don't migrate sessions from localstorage if we already have some
in indexeddb
2017-12-06 12:43:08 +00:00
David Baker 30e00d5fa7 Add impl to localstorage & memory store 2017-12-06 12:02:05 +00:00
David Baker bc99a9d792 Merge remote-tracking branch 'origin/develop' into dbkr/inbound_sessions_to_cryptostore 2017-12-06 10:05:33 +00:00
David Baker 61df41d21f Merge pull request #584 from matrix-org/dbkr/sessions_to_cryptostore
Move sessions to the crypto store
2017-12-06 10:04:33 +00:00
David Baker 0b4ef8dcbb Migrate inbound group sessions to crypto store 2017-12-05 21:47:22 +00:00
David Baker 9d7d48b9b5 s/sessionStore/cryptoStore/ 2017-12-05 14:04:18 +00:00
David Baker fb37150dfd Doc exception handling fun 2017-12-05 13:45:37 +00:00
David Baker bd08ed898d Maybe slightly better exception handling 2017-12-05 11:11:14 +00:00
David Baker d3bc525713 More doc 2017-12-05 10:23:26 +00:00
David Baker ae27c553ac More sensible loop & logging 2017-12-05 10:13:34 +00:00
David Baker 27030ae1e9 Use _unpickleSession in _getSession 2017-12-05 10:09:54 +00:00
David Baker fd083e1e66 doc 2017-12-05 10:04:06 +00:00
David Baker 5e4149ae76 Remove unused function 2017-12-04 18:14:56 +00:00
David Baker 859d462629 Lint 2017-12-04 18:08:38 +00:00
David Baker 8bfa81df42 Catch & rethrow decryption exceptions 2017-12-04 17:55:12 +00:00
David Baker 6782d53e28 Remove prefix from sessions store keys 2017-12-04 17:36:33 +00:00
David Baker 4796721d5c Migrate sessions from sessionStore 2017-12-04 17:12:13 +00:00
David Baker 55bbc71a17 Move sessionstore -> cryptostore migration
...into a separate function
2017-12-04 16:08:52 +00:00
David Baker 5372575b24 Implement session storage in memory & localstorage 2017-12-04 15:49:59 +00:00
David Baker d995019c6e lint 2017-12-04 14:40:32 +00:00
David Baker aa70da5659 Move sessions to the crypto store
This doesn't migrate existing ones yet
2017-12-04 14:33:03 +00:00
Luke Barnard 020b293068 Merge branch 'master' into develop 2017-12-04 11:58:30 +00:00
Luke Barnard 678ff23bcb v0.9.2 2017-12-04 11:57:05 +00:00
Luke Barnard f93d50dcd0 Prepare changelog for v0.9.2 2017-12-04 11:57:04 +00:00
Luke Barnard aa3201ebb0 Send correct m.visibility flag in js-sdk 2017-12-04 11:54:42 +00:00
Luke Barnard 4ad153c425 Send correct m.visibility flag in js-sdk 2017-12-01 18:01:23 +00:00
David Baker 082683bf0e Merge pull request #582 from matrix-org/dbkr/crypto_store_txn_api
Change crypto store transaction API
2017-11-30 13:45:28 +00:00
Luke Barnard 7f590af0b5 Add API wrapper for multiple device deletion API (#583) 2017-11-30 10:16:18 +00:00
David Baker ecc1c86600 Doc some more things 2017-11-29 17:40:48 +00:00
David Baker fece506cdd delint 2017-11-29 16:35:12 +00:00
David Baker f11a58e2cb Change crypto store transaction API
To allow multiple things to be fetched/stored in a single
transaction.

Currently it is still just the account that's actually in
indexeddb though.
2017-11-29 16:22:54 +00:00
David Baker 0238ecebed Fix comment 2017-11-28 11:32:34 +00:00
Richard van der Hoff 0d6ffa3935 Merge pull request #581 from matrix-org/dbkr/copyrights
Add some missed copyright notices
2017-11-27 16:04:07 +00:00
David Baker 143632e635 Add some missed copyright notices 2017-11-27 15:56:35 +00:00
David Baker 9ec33a97bb Merge pull request #579 from matrix-org/dbkr/e2e_indexeddb
Move Olm account to IndexedDB
2017-11-27 13:57:27 +00:00
David Baker 7e2c236582 Missed a s/account data/picked account/ 2017-11-27 13:46:31 +00:00
David Baker 6ebfd175bc jsdoc clarifications 2017-11-22 18:37:16 +00:00
David Baker defaa918a6 Remove unused function 2017-11-22 18:26:26 +00:00
David Baker c4e70be0a5 Better comment wording 2017-11-22 18:25:25 +00:00
David Baker 57d425fae6 Make the save function not return a promise
This was entirely unnecessary and hopefully make things a bit
simpler to understand and has fewer asyncs flying around.
2017-11-22 18:05:08 +00:00
David Baker 6024163af8 s/accountData/pickledAccount/ 2017-11-22 17:50:00 +00:00
David Baker 44b35cdb3d Lint 2017-11-22 16:53:21 +00:00
David Baker 36ff0ad019 Merge pull request #580 from matrix-org/rav/fix_crypto_error_logging
Fix logging of DecryptionErrors to be more useful
2017-11-22 16:44:09 +00:00
David Baker 9218e518f1 Add LocalStorageCryptoStore
To avoid throwing away all the data for anyone running firefox in
one of the modes where indexedDB is broken.
2017-11-22 16:41:52 +00:00
Richard van der Hoff c80bde1f60 Fix logging of DecryptionErrors to be more useful
We were relying on being able to override toString in DecryptionError, which
(a) doesn't work thanks to https://github.com/babel/babel/issues/3083, and (b)
was a bit naughty anyway. Instead, just add a detailedString property and use
that.
2017-11-22 14:42:32 +00:00
David Baker 4b7157b987 Remove unnecessary 'if' 2017-11-22 14:31:06 +00:00
David Baker a90f592224 Add comment on deprecation 2017-11-22 14:12:35 +00:00
David Baker 59f228dab7 Migrate account from session store 2017-11-22 14:07:19 +00:00
David Baker bae3f5ceb7 It's a heap, not a stack 2017-11-22 10:19:27 +00:00
David Baker a5c5da5b8a Lint 2017-11-22 10:18:53 +00:00
David Baker 7ecf313132 Use a callback function at the store layer
Rather than a promise which relies on the caller's promise handler
code being run in the same tick which is not guaranteed.
2017-11-22 10:04:27 +00:00
David Baker 313cfacfa1 Add comment 2017-11-21 18:40:25 +00:00
David Baker fb991503a9 Move OLM account to IndexedDBd
Wraps all access to the account in a transaction so any updates
done to the account must be done in the same transaction, making the
update atomic between tabs.

Doesn't do any migration from localstorage yet.
2017-11-21 18:27:40 +00:00
David Baker c31ce641a1 Merge branch 'master' into develop 2017-11-17 15:57:22 +00:00
David Baker 26e28ed687 Don't spuriously send unknown devices error
Send a sensible error message for other errors.
2017-11-17 11:48:56 +00:00
David Baker 8bf92d84db oops - didn't mean to remove that bit of doc 2017-11-16 18:12:09 +00:00
David Baker 9e2bb5b37b Allow answer to be called again after failing
* Store the answer we generate so if we fail to send it, we can
   try to send it again (doing the same again doesn't work as
   webrtc is in the wrong state).
 * Don't send ICE candidates if the call is ringing: queue them up
   so we can send them later if we manage to actually send the
   answer.
2017-11-16 16:29:45 +00:00
David Baker a48a88c312 Don't send a hangup on user media failure
We won't have sent the invite anyway. Also termainate before we
fire the error event so the call is 'ended' when the event handlers
fire (which means if they try to hang up it's also ignored)
2017-11-15 17:18:47 +00:00
David Baker 76b2fc2a6c Merge remote-tracking branch 'origin/develop' into dbkr/udd_no_auto_show 2017-11-15 12:10:06 +00:00
Luke Barnard 4438d716b9 Merge branch 'master' into develop 2017-11-15 10:57:57 +00:00
David Baker 8fcf55d761 BREAKING CHANGE: Fixes for unknown device errors
* If we can't send an invite due to unknown devices, abort the
   call.
 * Don't transition to the `invite_sent` state until the invite
   has actually sent.
 * Add a specific error code for failure due to unknown devices.
 * Don't send ICE candidate messages if the call has ended.
 * Add an `event` property to errors from `sendEvent` so that the
   caller can resend or cancel the event.
2017-11-15 10:56:57 +00:00
Matthew Hodgson a35d70e995 Merge pull request #568 from turt2live/travis/granular-settings
[BREAKING] Change the behaviour of the unverfied devices blacklist flag
2017-11-15 10:31:11 +00:00
Travis Ralston ec68000105 Merge branch 'develop' into travis/granular-settings 2017-11-14 19:19:01 -07:00
Matthew Hodgson c707d3db00 Merge pull request #557 from turt2live/travis/presence
Support set_presence=offline for syncing
2017-11-14 23:41:37 +00:00
Matthew Hodgson d3572836bd Merge pull request #556 from turt2live/travis/improved-redact-check
Consider cases where the sender may not redact their own event
2017-11-14 23:25:06 +00:00
David Baker a5dac751b0 BREAKING CHANGE: Remove send_event_error
Reverts https://github.com/matrix-org/matrix-js-sdk/pull/378

This swallowed all errors from sendEvent, breaking the ICE candidate
retrying. react-sdk no longer listens for send_event_error so I
think it's best to just remove this.
2017-11-13 17:51:59 +00:00
Travis Ralston 7a59579dcd Support sending additional options with acceptance (#570)
To be able to send things like `{visibility: { type: 'private ' }}`

Signed-off-by: Travis Ralston <travpc@gmail.com>
2017-11-10 14:14:11 +00:00
Travis Ralston f24b02cae4 It helps if you use the right function
Signed-off-by: Travis Ralston <travpc@gmail.com>
2017-11-09 11:01:42 -07:00
Travis Ralston 995f796a5d [BREAKING] Change the behaviour of the unverfied devices blacklist flag
Previously the global flag was used as a way to completely ignore the per-room option. This commit makes the per-room and global settings be more flexible to allow users to, for example, blacklist unverified devices in all room with the exception of one or two. This is done by making the global setting a device-level default and the per-room option allowing for 3 states: true, false, and unset (use device default).

Signed-off-by: Travis Ralston <travpc@gmail.com>
2017-11-08 17:47:45 -07:00
Travis Ralston 7c851faba6 Support set_presence=offline for syncing
Signed-off-by: Travis Ralston <travpc@gmail.com>
2017-10-14 14:34:37 -06:00
Travis Ralston b70c219a05 Consider cases where the sender may not redact their own event
Signed-off-by: Travis Ralston <travpc@gmail.com>
2017-10-13 20:00:39 -06:00
155 changed files with 37768 additions and 5230 deletions
+16 -10
View File
@@ -1,13 +1,19 @@
{
"presets": ["es2015"],
"plugins": [
// this transforms async functions into generator functions, which
// are then made to use the regenerator module by babel's
// transform-regnerator plugin (which is enabled by es2015).
"transform-async-to-bluebird",
// This makes sure that the regenerator runtime is available to
// the transpiled code.
"transform-runtime",
"sourceMaps": true,
"presets": [
["@babel/preset-env", {
"targets": {
"node": 10
},
"modules": "commonjs"
}],
"@babel/preset-typescript"
],
"plugins": [
"@babel/plugin-proposal-numeric-separator",
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-object-rest-spread",
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-transform-runtime"
]
}
+19 -2
View File
@@ -1,7 +1,6 @@
module.exports = {
parser: "babel-eslint",
parser: "babel-eslint", // now needed for class properties
parserOptions: {
ecmaVersion: 6,
sourceType: "module",
ecmaFeatures: {
}
@@ -13,8 +12,13 @@ module.exports = {
// babel's transform-runtime converts references to ES6 globals such as
// Promise and Map to core-js polyfills, so we can use ES6 globals.
es6: true,
jest: true,
},
extends: ["eslint:recommended", "google"],
plugins: [
"babel",
"jest",
],
rules: {
// rules we've always adhered to or now do
"max-len": ["error", {
@@ -51,6 +55,7 @@ module.exports = {
// rules we do not want from the google styleguide
"object-curly-spacing": ["off"],
"spaced-comment": ["off"],
"guard-for-in": ["off"],
// in principle we prefer single quotes, but life is too short
quotes: ["off"],
@@ -67,5 +72,17 @@ module.exports = {
"padded-blocks": ["warn"],
"no-extend-native": ["warn"],
"camelcase": ["warn"],
"no-multi-spaces": ["error", { "ignoreEOLComments": true }],
"space-before-function-paren": ["error", {
"anonymous": "never",
"named": "never",
"asyncArrow": "always",
}],
"arrow-parens": "off",
// eslint's built in no-invalid-this rule breaks with class properties
"no-invalid-this": "off",
// so we replace it with a version that is class property aware
"babel/no-invalid-this": "error",
}
}
+2
View File
@@ -0,0 +1,2 @@
patreon: matrixdotorg
liberapay: matrixdotorg
+4 -2
View File
@@ -2,16 +2,18 @@
/.jsdoc
node_modules
/.npmrc
/*.log
package-lock.json
.lock-wscript
build/Release
coverage
lib-cov
out
reports
/dist
/lib
/specbuild
# version file and tarball created by 'npm pack'
# version file and tarball created by `npm pack` / `yarn pack`
/git-revision.txt
/matrix-js-sdk-*.tgz
-5
View File
@@ -1,5 +0,0 @@
language: node_js
node_js:
- node # Latest stable version of nodejs.
script:
- ./travis.sh
+1853
View File
File diff suppressed because it is too large Load Diff
+26 -9
View File
@@ -24,7 +24,7 @@ works. Develop is the unstable branch where all the development actually
happens: the workflow is that contributors should fork the develop branch to
make a 'feature' branch for a particular contribution, and then make a pull
request to merge this back into the matrix.org 'official' develop branch. We
use github's pull request workflow to review the contribution, and either ask
use GitHub's pull request workflow to review the contribution, and either ask
you to make any refinements needed or merge it and make them ourselves. The
changes will then land on master when we next do a release.
@@ -36,8 +36,16 @@ minutes.
Code style
~~~~~~~~~~
The code-style for matrix-js-sdk is not formally documented, but contributors
are encouraged to read the code style document for matrix-react-sdk
The js-sdk aims to target TypeScript/ES6. All new files should be written in
TypeScript and existing files should use ES6 principles where possible.
Members should not be exported as a default export in general - it causes problems
with the architecture of the SDK (index file becomes less clear) and could
introduce naming problems (as default exports get aliased upon import). In
general, avoid using `export default`.
The remaining code-style for matrix-js-sdk is not formally documented, but
contributors are encouraged to read the code style document for matrix-react-sdk
(`<https://github.com/matrix-org/matrix-react-sdk/blob/master/code_style.md>`_)
and follow the principles set out there.
@@ -60,8 +68,8 @@ Sign off
~~~~~~~~
In order to have a concrete record that your contribution is intentional
and you agree to license it under the same terms as the project's license, we've adopted the
same lightweight approach that the Linux Kernel
and you agree to license it under the same terms as the project's license, we've
adopted the same lightweight approach that the Linux Kernel
(https://www.kernel.org/doc/Documentation/SubmittingPatches), Docker
(https://github.com/docker/docker/blob/master/CONTRIBUTING.md), and many other
projects use: the DCO (Developer Certificate of Origin:
@@ -109,7 +117,16 @@ include the line in your commit or pull request comment::
Signed-off-by: Your Name <your@email.example.org>
...using your real name; unfortunately pseudonyms and anonymous contributions
can't be accepted. Git makes this trivial - just use the -s flag when you do
``git commit``, having first set ``user.name`` and ``user.email`` git configs
(which you should have done anyway :)
We accept contributions under a legally identifiable name, such as your name on
government documentation or common-law names (names claimed by legitimate usage
or repute). Unfortunately, we cannot accept anonymous contributions at this
time.
Git allows you to add this signoff automatically when using the ``-s`` flag to
``git commit``, which uses the name and email set in your ``user.name`` and
``user.email`` git configs.
If you forgot to sign off your commits before making your pull request and are
on Git 2.17+ you can mass signoff using rebase::
git rebase --signoff origin/develop
+93 -34
View File
@@ -1,8 +1,7 @@
Matrix Javascript SDK
=====================
[![Build Status](http://matrix.org/jenkins/buildStatus/icon?job=JavascriptSDK)](http://matrix.org/jenkins/job/JavascriptSDK/)
This is the [Matrix](https://matrix.org) Client-Server v1/v2 alpha SDK for
This is the [Matrix](https://matrix.org) Client-Server r0 SDK for
JavaScript. This SDK can be run in a browser or in Node.js.
Quickstart
@@ -10,29 +9,94 @@ Quickstart
In a browser
------------
Download either the full or minified version from
Download the browser version from
https://github.com/matrix-org/matrix-js-sdk/releases/latest and add that as a
``<script>`` to your page. There will be a global variable ``matrixcs``
attached to ``window`` through which you can access the SDK. See below for how to
include libolm to enable end-to-end-encryption.
The browser bundle supports recent versions of browsers. Typically this is ES2015
or `> 0.5%, last 2 versions, Firefox ESR, not dead` if using
[browserlists](https://github.com/browserslist/browserslist).
Please check [the working browser example](examples/browser) for more information.
In Node.js
----------
``npm install matrix-js-sdk``
Ensure you have the latest LTS version of Node.js installed.
This SDK targets Node 10 for compatibility, which translates to ES6. If you're using
a bundler like webpack you'll likely have to transpile dependencies, including this
SDK, to match your target browsers.
Using `yarn` instead of `npm` is recommended. Please see the Yarn [install guide](https://yarnpkg.com/docs/install/)
if you do not have it already.
``yarn add matrix-js-sdk``
```javascript
var sdk = require("matrix-js-sdk");
var client = sdk.createClient("https://matrix.org");
import * as sdk from "matrix-js-sdk";
const client = sdk.createClient("https://matrix.org");
client.publicRooms(function(err, data) {
console.log("Public Rooms: %s", JSON.stringify(data));
});
```
See below for how to include libolm to enable end-to-end-encryption. Please check
[the Node.js terminal app](examples/node) for a more complex example.
To start the client:
```javascript
await client.startClient({initialSyncLimit: 10});
```
You can perform a call to `/sync` to get the current state of the client:
```javascript
client.once('sync', function(state, prevState, res) {
if(state === 'PREPARED') {
console.log("prepared");
} else {
console.log(state);
process.exit(1);
}
});
```
To send a message:
```javascript
const content = {
"body": "message text",
"msgtype": "m.text"
};
client.sendEvent("roomId", "m.room.message", content, "", (err, res) => {
console.log(err);
});
```
To listen for message events:
```javascript
client.on("Room.timeline", function(event, room, toStartOfTimeline) {
if (event.getType() !== "m.room.message") {
return; // only use messages
}
console.log(event.event.content.body);
});
```
By default, the `matrix-js-sdk` client uses the `MemoryStore` to store events as they are received. For example to iterate through the currently stored timeline for a room:
```javascript
Object.keys(client.store.rooms).forEach((roomId) => {
client.getRoom(roomId).timeline.forEach(t => {
console.log(t.event);
});
});
```
What does this SDK do?
----------------------
@@ -106,7 +170,7 @@ which will be fulfilled in the future.
The typical usage is something like:
```javascript
matrixClient.someMethod(arg1, arg2).done(function(result) {
matrixClient.someMethod(arg1, arg2).then(function(result) {
...
});
```
@@ -136,10 +200,10 @@ This section provides some useful code snippets which demonstrate the
core functionality of the SDK. These examples assume the SDK is setup like this:
```javascript
var sdk = require("matrix-js-sdk");
var myUserId = "@example:localhost";
var myAccessToken = "QGV4YW1wbGU6bG9jYWxob3N0.qPEvLuYfNBjxikiCjP";
var matrixClient = sdk.createClient({
import * as sdk from "matrix-js-sdk";
const myUserId = "@example:localhost";
const myAccessToken = "QGV4YW1wbGU6bG9jYWxob3N0.qPEvLuYfNBjxikiCjP";
const matrixClient = sdk.createClient({
baseUrl: "http://localhost:8008",
accessToken: myAccessToken,
userId: myUserId
@@ -151,7 +215,7 @@ core functionality of the SDK. These examples assume the SDK is setup like this:
```javascript
matrixClient.on("RoomMember.membership", function(event, member) {
if (member.membership === "invite" && member.userId === myUserId) {
matrixClient.joinRoom(member.roomId).done(function() {
matrixClient.joinRoom(member.roomId).then(function() {
console.log("Auto-joined %s", member.roomId);
});
}
@@ -192,11 +256,11 @@ Output:
```javascript
matrixClient.on("RoomState.members", function(event, state, member) {
var room = matrixClient.getRoom(state.roomId);
const room = matrixClient.getRoom(state.roomId);
if (!room) {
return;
}
var memberList = state.getMembers();
const memberList = state.getMembers();
console.log(room.name);
console.log(Array(room.name.length + 1).join("=")); // underline
for (var i = 0; i < memberList.length; i++) {
@@ -231,7 +295,7 @@ This SDK uses JSDoc3 style comments. You can manually build and
host the API reference from the source files like this:
```
$ npm run gendoc
$ yarn gendoc
$ cd .jsdoc
$ python -m SimpleHTTPServer 8005
```
@@ -242,8 +306,8 @@ End-to-end encryption support
=============================
The SDK supports end-to-end encryption via the Olm and Megolm protocols, using
[libolm](http://matrix.org/git/olm). It is left up to the application to make
libolm available, via the ``Olm`` global.
[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
``MatrixClient`` (but **before** calling ``matrixClient.startClient()``) to
@@ -262,20 +326,20 @@ specification.
To provide the Olm library in a browser application:
* download the transpiled libolm (from https://matrix.org/packages/npm/olm/).
* 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:
* ``npm install https://matrix.org/packages/npm/olm/olm-2.2.2.tgz``
* ``yarn add https://packages.matrix.org/npm/olm/olm-3.1.4.tgz``
(replace the URL with the latest version you want to use from
https://matrix.org/packages/npm/olm/)
https://packages.matrix.org/npm/olm/)
* ``global.Olm = require('olm');`` *before* loading ``matrix-js-sdk``.
If you want to package Olm as dependency for your node.js application, you
can use ``npm install https://matrix.org/packages/npm/olm/olm-2.2.2.tgz
--save-optional`` (if your application also works without e2e crypto enabled)
or ``--save`` (if it doesn't) to do so.
If you want to package Olm as dependency for your node.js application, you can
use ``yarn add https://packages.matrix.org/npm/olm/olm-3.1.4.tgz``. If your
application also works without e2e crypto enabled, add ``--optional`` to mark it
as an optional dependency.
Contributing
@@ -285,7 +349,7 @@ want to use this SDK, skip this section.*
First, you need to pull in the right build tools:
```
$ npm install
$ yarn install
```
Building
@@ -293,20 +357,15 @@ Building
To build a browser version from scratch when developing::
```
$ npm run build
```
To constantly do builds when files are modified (using ``watchify``)::
```
$ npm run watch
$ yarn build
```
To run tests (Jasmine)::
```
$ npm test
$ yarn test
```
To run linting:
```
$ npm run lint
$ yarn lint
```
-23
View File
@@ -1,23 +0,0 @@
var matrixcs = require("./lib/matrix");
matrixcs.request(require("browser-request"));
// just *accessing* indexedDB throws an exception in firefox with
// indexeddb disabled.
var indexedDB;
try {
indexedDB = global.indexedDB;
} catch(e) {}
// if our browser (appears to) support indexeddb, use an indexeddb crypto store.
if (indexedDB) {
matrixcs.setCryptoStoreFactory(
function() {
return new matrixcs.IndexedDBCryptoStore(
indexedDB, "matrix-js-sdk:crypto"
);
}
);
}
module.exports = matrixcs; // keep export for browserify package deps
global.matrixcs = matrixcs;
+70
View File
@@ -0,0 +1,70 @@
# Browser Storage Notes
## Overview
Browsers examined: Firefox 67, Chrome 75
The examination below applies to the default, non-persistent storage policy.
## Quota Measurement
Browsers appear to enforce and measure the quota in terms of space on disk, not
data stored, so you may be able to store more data than the simple sum of all
input data depending on how compressible your data is.
## Quota Limit
Specs and documentation suggest we should consistently receive
`QuotaExceededError` when we're near space limits, but the reality is a bit
blurrier.
When we are low on disk space overall or near the group limit / origin quota:
* Chrome
* Log database may fail to start with AbortError
* IndexedDB fails to start for crypto: AbortError in connect from
indexeddb-store-worker
* When near the quota, QuotaExceededError is used more consistently
* Firefox
* The first error will be QuotaExceededError
* Future write attempts will fail with various errors when space is low,
including nonsense like "InvalidStateError: A mutation operation was
attempted on a database that did not allow mutations."
* Once you start getting errors, the DB is effectively wedged in read-only
mode
* Can revive access if you reopen the DB
## Cache Eviction
While the Storage Standard says all storage for an origin group should be
limited by a single quota, in practice, browsers appear to handle `localStorage`
separately from the others, so it has a separate quota limit and isn't evicted
when low on space.
* Chrome, Firefox
* IndexedDB for origin deleted
* Local Storage remains in place
## Persistent Storage
Storage Standard offers a `navigator.storage.persist` API that can be used to
request persistent storage that won't be deleted by the browser because of low
space.
* Chrome
* Chrome 75 seems to grant this without any prompt based on [interaction
criteria](https://developers.google.com/web/updates/2016/06/persistent-storage)
* Firefox
* Firefox 67 shows a prompt to grant
* Reverting persistent seems to require revoking permission _and_ clearing
site data
## Storage Estimation
Storage Standard offers a `navigator.storage.estimate` API to get some clue of
how much space remains.
* Chrome, Firefox
* Can run this at any time to request an estimate of space remaining
* Firefox
* Returns `0` for `usage` if a site is persisted
-1
View File
@@ -1,4 +1,3 @@
"use strict";
console.log("Loading browser sdk");
var client = matrixcs.createClient("http://matrix.org");
+2
View File
@@ -0,0 +1,2 @@
olm.js
olm.wasm
+1
View File
@@ -0,0 +1 @@
../../../dist/browser-matrix.js
@@ -0,0 +1,59 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Test Crypto in Browser</title>
<script src="lib/olm.js"></script>
<script src="lib/matrix.js"></script>
</head>
<body>
<h1>Testing export/import of Olm devices in the browser</h1>
<ul>
<li>
Make sure you built the current version of the Matrix JS SDK
(<code>yarn build</code>)
</li>
<li>
copy <code>olm.js</code> and <code>olm.wasm</code>
from a recent release of Olm (was tested with version 3.1.4)
in directory <code>lib/</code>
</li>
<li>start a local Matrix homeserver (on port 8008, or change the port in the code)</li>
<li>Serve this HTML file (e.g. <code>python3 -m http.server</code>) and go to it through your browser</li>
<li>
in the JS console, do:
<pre>
aliceMatrixClient = await newMatrixClient("alice-"+randomHex());
await aliceMatrixClient.exportDevice();
await aliceMatrixClient.getAccessToken();
</pre>
</li>
<li>
copy the result of <code>exportDevice</code> and <code>getAccessToken</code> somewhere
(<strong>not</strong> in a JS variable as it will be destroyed when you refresh the page)
</li>
<li><strong>refresh the page (F5)</strong> to make sure the client is destroyed</li>
<li>
Do the following, replacing <code>ALICE_ID</code>
with the user ID of Alice (you can find it in the exported data)
<pre>
bobMatrixClient = await newMatrixClient("bob-"+randomHex());
roomId = await bobMatrixClient.createEncryptedRoom([ALICE_ID]);
await bobMatrixClient.sendTextMessage('Hi Alice!', roomId);
</pre>
</li>
<li>Again, <strong>refresh the page (F5)</strong>. You may want to clear your console as well.</li>
<li>
Now do the following, using the exported data and the access token you saved previously:
<pre>
aliceMatrixClient = await importMatrixClient(EXPORTED_DATA, ACCESS_TOKEN);
</pre>
</li>
<li>You should see the message sent by Bob printed in the console.</li>
</ul>
<script src="olm-device-export-import.js"></script>
</body>
</html>
@@ -0,0 +1,122 @@
if (!Olm) {
console.error(
"global.Olm does not seem to be present."
+ " Did you forget to add olm in the lib/ directory?"
);
}
const BASE_URL = 'http://localhost:8008';
const ROOM_CRYPTO_CONFIG = { algorithm: 'm.megolm.v1.aes-sha2' };
const PASSWORD = 'password';
// useful to create new usernames
window.randomHex = () => Math.floor(Math.random() * (10**6)).toString(16);
window.newMatrixClient = async function (username) {
const registrationClient = matrixcs.createClient(BASE_URL);
const userRegisterResult = await registrationClient.register(
username,
PASSWORD,
null,
{ type: 'm.login.dummy' }
);
const matrixClient = matrixcs.createClient({
baseUrl: BASE_URL,
userId: userRegisterResult.user_id,
accessToken: userRegisterResult.access_token,
deviceId: userRegisterResult.device_id,
sessionStore: new matrixcs.WebStorageSessionStore(window.localStorage),
cryptoStore: new matrixcs.MemoryCryptoStore(),
});
extendMatrixClient(matrixClient);
await matrixClient.initCrypto();
await matrixClient.startClient();
return matrixClient;
}
window.importMatrixClient = async function (exportedDevice, accessToken) {
const matrixClient = matrixcs.createClient({
baseUrl: BASE_URL,
deviceToImport: exportedDevice,
accessToken,
sessionStore: new matrixcs.WebStorageSessionStore(window.localStorage),
cryptoStore: new matrixcs.MemoryCryptoStore(),
});
extendMatrixClient(matrixClient);
await matrixClient.initCrypto();
await matrixClient.startClient();
return matrixClient;
}
function extendMatrixClient(matrixClient) {
// automatic join
matrixClient.on('RoomMember.membership', async (event, member) => {
if (member.membership === 'invite' && member.userId === matrixClient.getUserId()) {
await matrixClient.joinRoom(member.roomId);
// setting up of room encryption seems to be triggered automatically
// but if we don't wait for it the first messages we send are unencrypted
await matrixClient.setRoomEncryption(member.roomId, { algorithm: 'm.megolm.v1.aes-sha2' })
}
});
matrixClient.onDecryptedMessage = message => {
console.log('Got encrypted message: ', message);
}
matrixClient.on('Event.decrypted', (event) => {
if (event.getType() === 'm.room.message'){
matrixClient.onDecryptedMessage(event.getContent().body);
} else {
console.log('decrypted an event of type', event.getType());
console.log(event);
}
});
matrixClient.createEncryptedRoom = async function(usersToInvite) {
const {
room_id: roomId,
} = await this.createRoom({
visibility: 'private',
invite: usersToInvite,
});
// matrixClient.setRoomEncryption() only updates local state
// but does not send anything to the server
// (see https://github.com/matrix-org/matrix-js-sdk/issues/905)
// so we do it ourselves with 'sendStateEvent'
await this.sendStateEvent(
roomId, 'm.room.encryption', ROOM_CRYPTO_CONFIG,
);
await this.setRoomEncryption(
roomId, ROOM_CRYPTO_CONFIG,
);
// Marking all devices as verified
let room = this.getRoom(roomId);
let members = (await room.getEncryptionTargetMembers()).map(x => x["userId"])
let memberkeys = await this.downloadKeys(members);
for (const userId in memberkeys) {
for (const deviceId in memberkeys[userId]) {
await this.setDeviceVerified(userId, deviceId);
}
}
return roomId;
}
matrixClient.sendTextMessage = async function(message, roomId) {
return matrixClient.sendMessage(
roomId,
{
body: message,
msgtype: 'm.text',
}
)
}
}
+12 -14
View File
@@ -1,5 +1,3 @@
"use strict";
var myUserId = "@example:localhost";
var myAccessToken = "QGV4YW1wbGU6bG9jYWxob3N0.qPEvLuYfNBjxikiCjP";
var sdk = require("matrix-js-sdk");
@@ -56,7 +54,7 @@ rl.on('line', function(line) {
}
}
if (notSentEvent) {
matrixClient.resendEvent(notSentEvent, viewingRoom).done(function() {
matrixClient.resendEvent(notSentEvent, viewingRoom).then(function() {
printMessages();
rl.prompt();
}, function(err) {
@@ -70,7 +68,7 @@ rl.on('line', function(line) {
}
else if (line.indexOf("/more ") === 0) {
var amount = parseInt(line.split(" ")[1]) || 20;
matrixClient.scrollback(viewingRoom, amount).done(function(room) {
matrixClient.scrollback(viewingRoom, amount).then(function(room) {
printMessages();
rl.prompt();
}, function(err) {
@@ -79,7 +77,7 @@ rl.on('line', function(line) {
}
else if (line.indexOf("/invite ") === 0) {
var userId = line.split(" ")[1].trim();
matrixClient.invite(viewingRoom.roomId, userId).done(function() {
matrixClient.invite(viewingRoom.roomId, userId).then(function() {
printMessages();
rl.prompt();
}, function(err) {
@@ -92,7 +90,7 @@ rl.on('line', function(line) {
matrixClient.uploadContent({
stream: stream,
name: filename
}).done(function(url) {
}).then(function(url) {
var content = {
msgtype: "m.file",
body: filename,
@@ -116,7 +114,7 @@ rl.on('line', function(line) {
viewingRoom = roomList[roomIndex];
if (viewingRoom.getMember(myUserId).membership === "invite") {
// join the room first
matrixClient.joinRoom(viewingRoom.roomId).done(function(room) {
matrixClient.joinRoom(viewingRoom.roomId).then(function(room) {
setRoomList();
viewingRoom = room;
printMessages();
@@ -128,7 +126,7 @@ rl.on('line', function(line) {
else {
printMessages();
}
}
}
}
rl.prompt();
});
@@ -202,9 +200,9 @@ function printRoomList() {
dateStr = new Date(msg.getTs()).toISOString().replace(
/T/, ' ').replace(/\..+/, '');
}
var me = roomList[i].getMember(myUserId);
if (me) {
fmt = fmts[me.membership];
var myMembership = roomList[i].getMyMembership();
if (myMembership) {
fmt = fmts[myMembership];
}
var roomName = fixWidth(roomList[i].name, 25);
print(
@@ -281,8 +279,8 @@ function printMemberList(room) {
member.membership + new Array(10 - member.membership.length).join(" ")
);
print(
"%s"+fmt(" :: ")+"%s"+fmt(" (")+"%s"+fmt(")"),
membershipWithPadding, member.name,
"%s"+fmt(" :: ")+"%s"+fmt(" (")+"%s"+fmt(")"),
membershipWithPadding, member.name,
(member.userId === myUserId ? "Me" : member.userId),
fmt
);
@@ -295,7 +293,7 @@ function printRoomInfo(room) {
var sendHeader = " Sender ";
// pad content to 100
var restCount = (
100 - "Content".length - " | ".length - " | ".length -
100 - "Content".length - " | ".length - " | ".length -
eTypeHeader.length - sendHeader.length
);
var padSide = new Array(Math.floor(restCount/2)).join(" ");
-1
View File
@@ -1,4 +1,3 @@
"use strict";
console.log("Loading browser sdk");
var BASE_URL = "https://matrix.org";
var TOKEN = "accesstokengoeshere";
+1 -1
View File
@@ -21,4 +21,4 @@ export PATH="$rootdir/node_modules/.bin:$PATH"
# now run our checks
cd "$tmpdir"
npm run lint
yarn lint
-6
View File
@@ -1,6 +0,0 @@
var matrixcs = require("./lib/matrix");
matrixcs.request(require("request"));
module.exports = matrixcs;
var utils = require("./lib/utils");
utils.runPolyfills();
-34
View File
@@ -1,34 +0,0 @@
#!/bin/bash -l
set -x
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
nvm use 6 || exit $?
npm install || exit $?
RC=0
function fail {
echo $@ >&2
RC=1
}
# don't use last time's test reports
rm -rf reports coverage || exit $?
npm test || fail "npm test finished with return code $?"
npm run -s lint -- -f checkstyle > eslint.xml ||
fail "eslint finished with return code $?"
# delete the old tarball, if it exists
rm -f matrix-js-sdk-*.tgz
npm pack ||
fail "npm pack finished with return code $?"
npm run gendoc || fail "JSDoc failed with code $?"
exit $RC
+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"
}
}
+66 -56
View File
@@ -1,88 +1,98 @@
{
"name": "matrix-js-sdk",
"version": "0.9.1",
"version": "6.2.1",
"description": "Matrix Client-Server SDK for Javascript",
"main": "index.js",
"scripts": {
"test:build": "babel -s -d specbuild spec",
"test:run": "istanbul cover --report text --report cobertura --config .istanbul.yml -i \"lib/**/*.js\" node_modules/mocha/bin/_mocha -- --recursive specbuild --colors --reporter mocha-jenkins-reporter --reporter-options junit_report_path=reports/test-results.xml",
"test:watch": "mocha --watch --compilers js:babel-core/register --recursive spec --colors",
"test": "npm run test:build && npm run test:run",
"check": "npm run test:build && _mocha --recursive specbuild --colors",
"gendoc": "babel --no-babelrc -d .jsdocbuild src && jsdoc -r .jsdocbuild -P package.json -R README.md -d .jsdoc",
"start": "babel -s -w -d lib src",
"prepare": "yarn build",
"start": "echo THIS IS FOR LEGACY PURPOSES ONLY. && babel src -w -s -d lib --verbose --extensions \".ts,.js\"",
"dist": "echo 'This is for the release script so it can make assets (browser bundle).' && yarn build",
"clean": "rimraf lib dist",
"build": "babel -s -d lib src && rimraf dist && mkdir dist && browserify -d browser-index.js | exorcist dist/browser-matrix.js.map > dist/browser-matrix.js && uglifyjs -c -m -o dist/browser-matrix.min.js --source-map dist/browser-matrix.min.js.map --in-source-map dist/browser-matrix.js.map dist/browser-matrix.js",
"dist": "npm run build",
"watch": "watchify -d browser-index.js -o 'exorcist dist/browser-matrix.js.map > dist/browser-matrix.js' -v",
"lint": "eslint --max-warnings 109 src spec",
"prepublish": "npm run clean && npm run build && git rev-parse HEAD > git-revision.txt"
"build": "yarn clean && git rev-parse HEAD > git-revision.txt && yarn build:compile && yarn build:compile-browser && yarn build:minify-browser && yarn build:types",
"build:types": "tsc --emitDeclarationOnly",
"build:compile": "babel -d lib --verbose --extensions \".ts,.js\" src",
"build:compile-browser": "mkdirp dist && browserify -d src/browser-index.js -p [ tsify -p ./tsconfig.json ] -t [ babelify --sourceMaps=inline --presets [ @babel/preset-env @babel/preset-typescript ] ] | exorcist dist/browser-matrix.js.map > dist/browser-matrix.js",
"build:minify-browser": "terser dist/browser-matrix.js --compress --mangle --source-map --output dist/browser-matrix.min.js",
"gendoc": "jsdoc -c jsdoc.json -P package.json",
"lint": "yarn lint:types && yarn lint:ts && yarn lint:js",
"lint:js": "eslint --max-warnings 81 src spec",
"lint:types": "tsc --noEmit",
"lint:ts": "tslint --project ./tsconfig.json -t stylish",
"test": "jest spec/ --coverage --testEnvironment node",
"test:watch": "jest spec/ --coverage --testEnvironment node --watch"
},
"repository": {
"type": "git",
"url": "https://github.com/matrix-org/matrix-js-sdk"
},
"keywords": [
"matrix-org"
],
"browser": "browser-index.js",
"main": "./lib/index.js",
"typings": "./lib/index.d.ts",
"browser": "./lib/browser-index.js",
"matrix_src_main": "./src/index.ts",
"matrix_src_browser": "./src/browser-index.js",
"author": "matrix.org",
"license": "Apache-2.0",
"files": [
".babelrc",
".eslintrc.js",
"spec/.eslintrc.js",
"dist",
"lib",
"src",
"git-revision.txt",
"CHANGELOG.md",
"CONTRIBUTING.rst",
"LICENSE",
"README.md",
"RELEASING.md",
"examples",
"git-hooks",
"git-revision.txt",
"index.js",
"browser-index.js",
"jenkins.sh",
"lib",
"package.json",
"release.sh",
"spec",
"src"
"release.sh"
],
"dependencies": {
"@babel/runtime": "^7.8.3",
"another-json": "^0.2.0",
"babel-runtime": "^6.26.0",
"bluebird": "^3.5.0",
"browser-request": "^0.3.3",
"bs58": "^4.0.1",
"content-type": "^1.0.2",
"request": "^2.53.0"
"loglevel": "^1.6.4",
"qs": "^6.5.2",
"request": "^2.88.0",
"unhomoglyph": "^1.0.2"
},
"devDependencies": {
"babel-cli": "^6.18.0",
"babel-eslint": "^7.1.1",
"babel-plugin-transform-async-to-bluebird": "^1.1.1",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-es2015": "^6.18.0",
"browserify": "^14.0.0",
"browserify-shim": "^3.8.13",
"eslint": "^3.13.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.8.3",
"@babel/preset-env": "^7.7.6",
"@babel/preset-typescript": "^7.7.4",
"@babel/register": "^7.7.4",
"@types/node": "12",
"@types/request": "^2.48.4",
"babel-eslint": "^10.0.3",
"babel-jest": "^24.9.0",
"babelify": "^10.0.0",
"better-docs": "^1.4.7",
"browserify": "^16.5.0",
"eslint": "^5.12.0",
"eslint-config-google": "^0.7.1",
"exorcist": "^0.4.0",
"expect": "^1.20.2",
"istanbul": "^0.4.5",
"eslint-plugin-babel": "^5.3.0",
"eslint-plugin-jest": "^23.0.4",
"exorcist": "^1.0.1",
"fake-indexeddb": "^3.0.0",
"jest": "^24.9.0",
"jest-localstorage-mock": "^2.4.0",
"jsdoc": "^3.5.5",
"lolex": "^1.5.2",
"matrix-mock-request": "^1.2.0",
"mocha": "^3.2.0",
"mocha-jenkins-reporter": "^0.3.6",
"rimraf": "^2.5.4",
"source-map-support": "^0.4.11",
"sourceify": "^0.1.0",
"uglify-js": "^2.8.26",
"watchify": "^3.2.1"
"matrix-mock-request": "^1.2.3",
"olm": "https://packages.matrix.org/npm/olm/olm-3.1.4.tgz",
"rimraf": "^3.0.0",
"terser": "^4.4.3",
"tsify": "^4.0.1",
"tslint": "^5.20.1",
"typescript": "^3.7.3"
},
"browserify": {
"transform": [
"sourceify"
]
"jest": {
"testEnvironment": "node"
}
}
+82 -22
View File
@@ -1,17 +1,33 @@
#!/bin/bash
#
# Script to perform a release of matrix-js-sdk.
# Script to perform a release of matrix-js-sdk and downstream projects.
#
# Requires:
# github-changelog-generator; install via:
# pip install git+https://github.com/matrix-org/github-changelog-generator.git
# jq; install from your distribution's package manager (https://stedolan.github.io/jq/)
# hub; install via brew (OSX) or source/pre-compiled binaries (debian) (https://github.com/github/hub) - Tested on v2.2.9
# hub; install via brew (macOS) or source/pre-compiled binaries (debian) (https://github.com/github/hub) - Tested on v2.2.9
# npm; typically installed by Node.js
# yarn; install via brew (macOS) or similar (https://yarnpkg.com/docs/install/)
#
# Note: this script is also used to release matrix-react-sdk and riot-web.
set -e
jq --version > /dev/null || (echo "jq is required: please install it"; kill $$)
hub --version > /dev/null || (echo "hub is required: please install it"; kill $$)
if [[ `command -v hub` ]] && [[ `hub --version` =~ hub[[:space:]]version[[:space:]]([0-9]*).([0-9]*) ]]; then
HUB_VERSION_MAJOR=${BASH_REMATCH[1]}
HUB_VERSION_MINOR=${BASH_REMATCH[2]}
if [[ $HUB_VERSION_MAJOR -lt 2 ]] || [[ $HUB_VERSION_MAJOR -eq 2 && $HUB_VERSION_MINOR -lt 5 ]]; then
echo "hub version 2.5 is required, you have $HUB_VERSION_MAJOR.$HUB_VERSION_MINOR installed"
exit
fi
else
echo "hub is required: please install it"
exit
fi
npm --version > /dev/null || (echo "npm is required: please install it"; kill $$)
yarn --version > /dev/null || (echo "yarn is required: please install it"; kill $$)
USAGE="$0 [-xz] [-c changelog_file] vX.Y.Z"
@@ -22,6 +38,7 @@ $USAGE
-c changelog_file: specify name of file containing changelog
-x: skip updating the changelog
-z: skip generating the jsdoc
-n: skip publish to NPM
EOF
}
@@ -44,8 +61,10 @@ fi
skip_changelog=
skip_jsdoc=
skip_npm=
changelog_file="CHANGELOG.md"
while getopts hc:xz f; do
expected_npm_user="matrixdotorg"
while getopts hc:u:xzn f; do
case $f in
h)
help
@@ -60,6 +79,12 @@ while getopts hc:xz f; do
z)
skip_jsdoc=1
;;
n)
skip_npm=1
;;
u)
expected_npm_user="$OPTARG"
;;
esac
done
shift `expr $OPTIND - 1`
@@ -74,6 +99,16 @@ if [ -z "$skip_changelog" ]; then
update_changelog -h > /dev/null || (echo "github-changelog-generator is required: please install it"; exit)
fi
# Login and publish continues to use `npm`, as it seems to have more clearly
# defined options and semantics than `yarn` for writing to the registry.
if [ -z "$skip_npm" ]; then
actual_npm_user=`npm whoami`;
if [ $expected_npm_user != $actual_npm_user ]; then
echo "you need to be logged into npm as $expected_npm_user, but you are logged in as $actual_npm_user" >&2
exit 1
fi
fi
# ignore leading v on release
release="${1#v}"
tag="v${release}"
@@ -127,14 +162,22 @@ cat "${changelog_file}" | `dirname $0`/scripts/changelog_head.py > "${latest_cha
set -x
# Bump package.json and build the dist
echo "npm version"
# npm version will automatically commit its modification
echo "yarn version"
# yarn version will automatically commit its modification
# and make a release tag. We don't want it to create the tag
# because it can only sign with the default key, but we can
# only turn off both of these behaviours, so we have to
# manually commit the result.
npm version --no-git-tag-version "$release"
git commit package.json -m "$tag"
yarn version --no-git-tag-version --new-version "$release"
# commit yarn.lock if it exists, is versioned, and is modified
if [[ -f yarn.lock && `git status --porcelain yarn.lock | grep '^ M'` ]];
then
pkglock='yarn.lock'
else
pkglock=''
fi
git commit package.json $pkglock -m "$tag"
# figure out if we should be signing this release
@@ -150,7 +193,7 @@ fi
# assets.
# We make a completely separate checkout to be sure
# we're using released versions of the dependencies
# (rather than whatever we're pulling in from npm link)
# (rather than whatever we're pulling in from yarn link)
assets=''
dodist=0
jq -e .scripts.dist package.json 2> /dev/null || dodist=$?
@@ -161,10 +204,15 @@ if [ $dodist -eq 0 ]; then
pushd "$builddir"
git clone "$projdir" .
git checkout "$rel_branch"
npm install
# We use Git branch / commit dependencies for some packages, and Yarn seems
# to have a hard time getting that right. See also
# https://github.com/yarnpkg/yarn/issues/4734. As a workaround, we clean the
# global cache here to ensure we get the right thing.
yarn cache clean
yarn install
# We haven't tagged yet, so tell the dist script what version
# it's building
DIST_VERSION="$tag" npm run dist
DIST_VERSION="$tag" yarn dist
popd
@@ -245,7 +293,7 @@ release_text=`mktemp`
echo "$tag" > "${release_text}"
echo >> "${release_text}"
cat "${latest_changes}" >> "${release_text}"
hub release create $hubflags $assets -f "${release_text}" "$tag"
hub release create $hubflags $assets -F "${release_text}" "$tag"
if [ $dodist -eq 0 ]; then
rm -rf "$builddir"
@@ -253,12 +301,22 @@ fi
rm "${release_text}"
rm "${latest_changes}"
# publish to npmjs
npm publish
# Login and publish continues to use `npm`, as it seems to have more clearly
# defined options and semantics than `yarn` for writing to the registry.
# Tag both releases and prereleases as `next` so the last stable release remains
# the default.
if [ -z "$skip_npm" ]; then
npm publish --tag next
if [ $prerelease -eq 0 ]; then
# For a release, also add the default `latest` tag.
package=$(cat package.json | jq -er .name)
npm dist-tag add "$package@$release" latest
fi
fi
if [ -z "$skip_jsdoc" ]; then
echo "generating jsdocs"
npm run gendoc
yarn gendoc
echo "copying jsdocs to gh-pages branch"
git checkout gh-pages
@@ -281,16 +339,18 @@ fi
echo "updating master branch"
git checkout master
git pull
git merge --ff-only "$rel_branch"
git merge "$rel_branch"
# push master and docs (if generated) to github
# push master and docs (if generated) to github
git push origin master
if [ -z "$skip_jsdoc" ]; then
git push origin gh-pages
fi
# finally, merge master back onto develop
git checkout develop
git pull
git merge master
git push origin develop
# finally, merge master back onto develop (if it exists)
if [ $(git branch -lr | grep origin/develop -c) -ge 1 ]; then
git checkout develop
git pull
git merge master
git push origin develop
fi
-5
View File
@@ -1,5 +0,0 @@
module.exports = {
env: {
mocha: true,
},
}
+2 -3
View File
@@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -18,7 +19,7 @@ limitations under the License.
* A mock implementation of the webstorage api
* @constructor
*/
function MockStorageApi() {
export function MockStorageApi() {
this.data = {};
this.keys = [];
this.length = 0;
@@ -52,5 +53,3 @@ MockStorageApi.prototype = {
},
};
/** */
module.exports = MockStorageApi;
+39 -23
View File
@@ -1,6 +1,7 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2018-2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -15,16 +16,16 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
"use strict";
// load olm before the sdk if possible
import './olm-loader';
import sdk from '..';
import testUtils from './test-utils';
import MockHttpBackend from 'matrix-mock-request';
import expect from 'expect';
import Promise from 'bluebird';
import {LocalStorageCryptoStore} from '../src/crypto/store/localStorage-crypto-store';
import {logger} from '../src/logger';
import {WebStorageSessionStore} from "../src/store/session/webstorage";
import {syncPromise} from "./test-utils";
import {createClient} from "../src/matrix";
import {MockStorageApi} from "./MockStorageApi";
/**
* Wrapper for a MockStorageApi, MockHttpBackend and MatrixClient
@@ -36,26 +37,35 @@ import Promise from 'bluebird';
*
* @param {WebStorage=} sessionStoreBackend a web storage object to use for the
* session store. If undefined, we will create a MockStorageApi.
* @param {object} options additional options to pass to the client
*/
export default function TestClient(
userId, deviceId, accessToken, sessionStoreBackend,
export function TestClient(
userId, deviceId, accessToken, sessionStoreBackend, options,
) {
this.userId = userId;
this.deviceId = deviceId;
if (sessionStoreBackend === undefined) {
sessionStoreBackend = new testUtils.MockStorageApi();
sessionStoreBackend = new MockStorageApi();
}
this.storage = new sdk.WebStorageSessionStore(sessionStoreBackend);
const sessionStore = new WebStorageSessionStore(sessionStoreBackend);
this.httpBackend = new MockHttpBackend();
this.client = sdk.createClient({
options = Object.assign({
baseUrl: "http://" + userId + ".test.server",
userId: userId,
accessToken: accessToken,
deviceId: deviceId,
sessionStore: this.storage,
sessionStore: sessionStore,
request: this.httpBackend.requestFn,
});
}, options);
if (!options.cryptoStore) {
// expose this so the tests can get to it
this.cryptoStore = new LocalStorageCryptoStore(sessionStoreBackend);
options.cryptoStore = this.cryptoStore;
}
this.client = createClient(options);
this.deviceKeys = null;
this.oneTimeKeys = {};
@@ -71,7 +81,7 @@ TestClient.prototype.toString = function() {
* @return {Promise}
*/
TestClient.prototype.start = function() {
console.log(this + ': starting');
logger.log(this + ': starting');
this.httpBackend.when("GET", "/pushrules").respond(200, {});
this.httpBackend.when("POST", "/filter").respond(200, { filter_id: "fid" });
this.expectDeviceKeyUpload();
@@ -87,17 +97,19 @@ TestClient.prototype.start = function() {
return Promise.all([
this.httpBackend.flushAllExpected(),
testUtils.syncPromise(this.client),
syncPromise(this.client),
]).then(() => {
console.log(this + ': started');
logger.log(this + ': started');
});
};
/**
* stop the client
* @return {Promise} Resolves once the mock http backend has finished all pending flushes
*/
TestClient.prototype.stop = function() {
this.client.stopClient();
return this.httpBackend.stop();
};
/**
@@ -109,7 +121,7 @@ TestClient.prototype.expectDeviceKeyUpload = function() {
expect(content.one_time_keys).toBe(undefined);
expect(content.device_keys).toBeTruthy();
console.log(self + ': received device keys');
logger.log(self + ': received device keys');
// we expect this to happen before any one-time keys are uploaded.
expect(Object.keys(self.oneTimeKeys).length).toEqual(0);
@@ -145,8 +157,8 @@ TestClient.prototype.awaitOneTimeKeyUpload = function() {
.respond(200, (path, content) => {
expect(content.device_keys).toBe(undefined);
expect(content.one_time_keys).toBeTruthy();
expect(content.one_time_keys).toNotEqual({});
console.log('%s: received %i one-time keys', this,
expect(content.one_time_keys).not.toEqual({});
logger.log('%s: received %i one-time keys', this,
Object.keys(content.one_time_keys).length);
this.oneTimeKeys = content.one_time_keys;
return {one_time_key_counts: {
@@ -172,7 +184,11 @@ TestClient.prototype.expectKeyQuery = function(response) {
this.httpBackend.when('POST', '/keys/query').respond(
200, (path, content) => {
Object.keys(response.device_keys).forEach((userId) => {
expect(content.device_keys[userId]).toEqual({});
expect(content.device_keys[userId]).toEqual(
[],
"Expected key query for " + userId + ", got " +
Object.keys(content.device_keys),
);
});
return response;
});
@@ -206,11 +222,11 @@ TestClient.prototype.getSigningKey = function() {
* @returns {Promise} promise which completes once the sync has been flushed
*/
TestClient.prototype.flushSync = function() {
console.log(`${this}: flushSync`);
logger.log(`${this}: flushSync`);
return Promise.all([
this.httpBackend.flush('/sync', 1),
testUtils.syncPromise(this.client),
syncPromise(this.client),
]).then(() => {
console.log(`${this}: flushSync completed`);
logger.log(`${this}: flushSync completed`);
});
};
+23
View File
@@ -0,0 +1,23 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
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.
*/
// stub for browser-matrix browserify tests
global.XMLHttpRequest = jest.fn();
afterAll(() => {
// clean up XMLHttpRequest mock
global.XMLHttpRequest = undefined;
});
+103
View File
@@ -0,0 +1,103 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
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.
*/
// load XmlHttpRequest mock
import "./setupTests";
import "../../dist/browser-matrix"; // uses browser-matrix instead of the src
import {MockStorageApi} from "../MockStorageApi";
import {WebStorageSessionStore} from "../../src/store/session/webstorage";
import MockHttpBackend from "matrix-mock-request";
import {LocalStorageCryptoStore} from "../../src/crypto/store/localStorage-crypto-store";
import * as utils from "../test-utils";
const USER_ID = "@user:test.server";
const DEVICE_ID = "device_id";
const ACCESS_TOKEN = "access_token";
const ROOM_ID = "!room_id:server.test";
/* global matrixcs */
describe("Browserify Test", function() {
let client;
let httpBackend;
async function createTestClient() {
const sessionStoreBackend = new MockStorageApi();
const sessionStore = new WebStorageSessionStore(sessionStoreBackend);
const httpBackend = new MockHttpBackend();
const options = {
baseUrl: "http://" + USER_ID + ".test.server",
userId: USER_ID,
accessToken: ACCESS_TOKEN,
deviceId: DEVICE_ID,
sessionStore: sessionStore,
request: httpBackend.requestFn,
cryptoStore: new LocalStorageCryptoStore(sessionStoreBackend),
};
const client = matrixcs.createClient(options);
httpBackend.when("GET", "/pushrules").respond(200, {});
httpBackend.when("POST", "/filter").respond(200, { filter_id: "fid" });
return { client, httpBackend };
}
beforeEach(async () => {
({client, httpBackend} = await createTestClient());
await client.startClient();
});
afterEach(async () => {
client.stopClient();
await httpBackend.stop();
});
it("Sync", async function() {
const event = utils.mkMembership({
room: ROOM_ID,
mship: "join",
user: "@other_user:server.test",
name: "Displayname",
});
const syncData = {
next_batch: "batch1",
rooms: {
join: {},
},
};
syncData.rooms.join[ROOM_ID] = {
timeline: {
events: [
event,
],
limited: false,
},
};
httpBackend.when("GET", "/sync").respond(200, syncData);
await Promise.race([
Promise.all([
httpBackend.flushAllExpected(),
]),
new Promise((_, reject) => {
client.once("sync.unexpectedError", reject);
}),
]);
}, 10000);
});
+97 -67
View File
@@ -1,8 +1,24 @@
import expect from 'expect';
import Promise from 'bluebird';
/*
Copyright 2017 Vector Creations Ltd
Copyright 2018 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
import TestClient from '../TestClient';
import testUtils from '../test-utils';
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 {TestClient} from '../TestClient';
import * as testUtils from '../test-utils';
import {logger} from '../../src/logger';
const ROOM_ID = "!room:id";
@@ -54,7 +70,7 @@ function getSyncResponse(roomMembers) {
describe("DeviceList management:", function() {
if (!global.Olm) {
console.warn('not running deviceList tests: Olm not present');
logger.warn('not running deviceList tests: Olm not present');
return;
}
@@ -70,8 +86,6 @@ describe("DeviceList management:", function() {
}
beforeEach(async function() {
testUtils.beforeEach(this); // eslint-disable-line no-invalid-this
// we create our own sessionStoreBackend so that we can use it for
// another TestClient.
sessionStoreBackend = new testUtils.MockStorageApi();
@@ -80,17 +94,18 @@ describe("DeviceList management:", function() {
});
afterEach(function() {
aliceTestClient.stop();
return aliceTestClient.stop();
});
it("Alice shouldn't do a second /query for non-e2e-capable devices", function() {
aliceTestClient.expectKeyQuery({device_keys: {'@alice:localhost': {}}});
return aliceTestClient.start().then(function() {
const syncResponse = getSyncResponse(['@bob:xyz']);
aliceTestClient.httpBackend.when('GET', '/sync').respond(200, syncResponse);
return aliceTestClient.flushSync();
}).then(function() {
console.log("Forcing alice to download our device keys");
logger.log("Forcing alice to download our device keys");
aliceTestClient.httpBackend.when('POST', '/keys/query').respond(200, {
device_keys: {
@@ -103,7 +118,7 @@ describe("DeviceList management:", function() {
aliceTestClient.httpBackend.flush('/keys/query', 1),
]);
}).then(function() {
console.log("Telling alice to send a megolm message");
logger.log("Telling alice to send a megolm message");
aliceTestClient.httpBackend.when(
'PUT', '/send/',
@@ -126,6 +141,7 @@ describe("DeviceList management:", function() {
it("We should not get confused by out-of-order device query responses",
() => {
// https://github.com/vector-im/riot-web/issues/3126
aliceTestClient.expectKeyQuery({device_keys: {'@alice:localhost': {}}});
return aliceTestClient.start().then(() => {
aliceTestClient.httpBackend.when('GET', '/sync').respond(
200, getSyncResponse(['@bob:xyz', '@chris:abc']));
@@ -151,9 +167,12 @@ describe("DeviceList management:", function() {
aliceTestClient.httpBackend.flush('/keys/query', 1).then(
() => aliceTestClient.httpBackend.flush('/send/', 1),
),
aliceTestClient.client._crypto._deviceList.saveIfDirty(),
]);
}).then(() => {
expect(aliceTestClient.storage.getEndToEndDeviceSyncToken()).toEqual(1);
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
expect(data.syncToken).toEqual(1);
});
// invalidate bob's and chris's device lists in separate syncs
aliceTestClient.httpBackend.when('GET', '/sync').respond(200, {
@@ -185,19 +204,21 @@ describe("DeviceList management:", function() {
return aliceTestClient.httpBackend.flush('/keys/query', 1);
}).then((flushed) => {
expect(flushed).toEqual(0);
const bobStat = aliceTestClient.storage
.getEndToEndDeviceTrackingStatus()['@bob:xyz'];
if (bobStat != 1 && bobStat != 2) {
throw new Error('Unexpected status for bob: wanted 1 or 2, got ' +
bobStat);
}
const chrisStat = aliceTestClient.storage
.getEndToEndDeviceTrackingStatus()['@chris:abc'];
if (chrisStat != 1 && chrisStat != 2) {
throw new Error('Unexpected status for chris: wanted 1 or 2, got ' +
chrisStat);
}
return aliceTestClient.client._crypto._deviceList.saveIfDirty();
}).then(() => {
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
const bobStat = data.trackingStatus['@bob:xyz'];
if (bobStat != 1 && bobStat != 2) {
throw new Error('Unexpected status for bob: wanted 1 or 2, got ' +
bobStat);
}
const chrisStat = data.trackingStatus['@chris:abc'];
if (chrisStat != 1 && chrisStat != 2) {
throw new Error(
'Unexpected status for chris: wanted 1 or 2, got ' + chrisStat,
);
}
});
// now add an expectation for a query for bob's devices, and let
// it complete.
@@ -216,15 +237,18 @@ describe("DeviceList management:", function() {
// wait for the client to stop processing the response
return aliceTestClient.client.downloadKeys(['@bob:xyz']);
}).then(() => {
const bobStat = aliceTestClient.storage
.getEndToEndDeviceTrackingStatus()['@bob:xyz'];
expect(bobStat).toEqual(3);
const chrisStat = aliceTestClient.storage
.getEndToEndDeviceTrackingStatus()['@chris:abc'];
if (chrisStat != 1 && chrisStat != 2) {
throw new Error('Unexpected status for chris: wanted 1 or 2, got ' +
bobStat);
}
return aliceTestClient.client._crypto._deviceList.saveIfDirty();
}).then(() => {
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
const bobStat = data.trackingStatus['@bob:xyz'];
expect(bobStat).toEqual(3);
const chrisStat = data.trackingStatus['@chris:abc'];
if (chrisStat != 1 && chrisStat != 2) {
throw new Error(
'Unexpected status for chris: wanted 1 or 2, got ' + bobStat,
);
}
});
// now let the query for chris's devices complete.
return aliceTestClient.httpBackend.flush('/keys/query', 1);
@@ -234,16 +258,18 @@ describe("DeviceList management:", function() {
// wait for the client to stop processing the response
return aliceTestClient.client.downloadKeys(['@chris:abc']);
}).then(() => {
const bobStat = aliceTestClient.storage
.getEndToEndDeviceTrackingStatus()['@bob:xyz'];
const chrisStat = aliceTestClient.storage
.getEndToEndDeviceTrackingStatus()['@chris:abc'];
return aliceTestClient.client._crypto._deviceList.saveIfDirty();
}).then(() => {
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
const bobStat = data.trackingStatus['@bob:xyz'];
const chrisStat = data.trackingStatus['@bob:xyz'];
expect(bobStat).toEqual(3);
expect(chrisStat).toEqual(3);
expect(aliceTestClient.storage.getEndToEndDeviceSyncToken()).toEqual(3);
expect(bobStat).toEqual(3);
expect(chrisStat).toEqual(3);
expect(data.syncToken).toEqual(3);
});
});
});
}).timeout(3000);
// https://github.com/vector-im/riot-web/issues/4983
describe("Alice should know she has stale device lists", () => {
@@ -262,13 +288,15 @@ describe("DeviceList management:", function() {
},
);
await aliceTestClient.httpBackend.flush('/keys/query', 1);
await aliceTestClient.client._crypto._deviceList.saveIfDirty();
const bobStat = aliceTestClient.storage
.getEndToEndDeviceTrackingStatus()['@bob:xyz'];
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
const bobStat = data.trackingStatus['@bob:xyz'];
expect(bobStat).toBeGreaterThan(
0, "Alice should be tracking bob's device list",
);
expect(bobStat).toBeGreaterThan(
0, "Alice should be tracking bob's device list",
);
});
});
it("when Bob leaves", async function() {
@@ -297,12 +325,15 @@ describe("DeviceList management:", function() {
await aliceTestClient.flushSync();
await aliceTestClient.client._crypto._deviceList.saveIfDirty();
const bobStat = aliceTestClient.storage
.getEndToEndDeviceTrackingStatus()['@bob:xyz'];
expect(bobStat).toEqual(
0, "Alice should have marked bob's device list as untracked",
);
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
const bobStat = data.trackingStatus['@bob:xyz'];
expect(bobStat).toEqual(
0, "Alice should have marked bob's device list as untracked",
);
});
});
it("when Alice leaves", async function() {
@@ -330,12 +361,15 @@ describe("DeviceList management:", function() {
);
await aliceTestClient.flushSync();
await aliceTestClient.client._crypto._deviceList.saveIfDirty();
const bobStat = aliceTestClient.storage
.getEndToEndDeviceTrackingStatus()['@bob:xyz'];
expect(bobStat).toEqual(
0, "Alice should have marked bob's device list as untracked",
);
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
const bobStat = data.trackingStatus['@bob:xyz'];
expect(bobStat).toEqual(
0, "Alice should have marked bob's device list as untracked",
);
});
});
it("when Bob leaves whilst Alice is offline", async function() {
@@ -344,23 +378,19 @@ describe("DeviceList management:", function() {
const anotherTestClient = await createTestClient();
try {
anotherTestClient.httpBackend.when('GET', '/keys/changes').respond(
200, {
changed: [],
left: ['@bob:xyz'],
},
);
await anotherTestClient.start();
anotherTestClient.httpBackend.when('GET', '/sync').respond(
200, getSyncResponse([]));
await anotherTestClient.flushSync();
await anotherTestClient.client._crypto._deviceList.saveIfDirty();
const bobStat = anotherTestClient.storage
.getEndToEndDeviceTrackingStatus()['@bob:xyz'];
anotherTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
const bobStat = data.trackingStatus['@bob:xyz'];
expect(bobStat).toEqual(
0, "Alice should have marked bob's device list as untracked",
);
expect(bobStat).toEqual(
0, "Alice should have marked bob's device list as untracked",
);
});
} finally {
anotherTestClient.stop();
}
+81 -69
View File
@@ -1,6 +1,8 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2018 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -23,18 +25,14 @@ limitations under the License.
* See also `megolm.spec.js`.
*/
"use strict";
import 'source-map-support/register';
// load olm before the sdk if possible
import '../olm-loader';
import expect from 'expect';
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 * as testUtils from "../test-utils";
import * as utils from "../../src/utils";
import {TestClient} from "../TestClient";
import {CRYPTO_ENABLED} from "../../src/client";
let aliTestClient;
const roomId = "!room:localhost";
@@ -54,7 +52,7 @@ function bobUploadsDeviceKeys() {
bobTestClient.client.uploadKeys(),
bobTestClient.httpBackend.flush(),
]).then(() => {
expect(Object.keys(bobTestClient.deviceKeys).length).toNotEqual(0);
expect(Object.keys(bobTestClient.deviceKeys).length).not.toEqual(0);
});
}
@@ -71,7 +69,11 @@ function expectAliQueryKeys() {
bobKeys[bobDeviceId] = bobTestClient.deviceKeys;
aliTestClient.httpBackend.when("POST", "/keys/query")
.respond(200, function(path, content) {
expect(content.device_keys[bobUserId]).toEqual({});
expect(content.device_keys[bobUserId]).toEqual(
[],
"Expected Alice to key query for " + bobUserId + ", got " +
Object.keys(content.device_keys),
);
const result = {};
result[bobUserId] = bobKeys;
return {device_keys: result};
@@ -90,12 +92,16 @@ function expectBobQueryKeys() {
const aliKeys = {};
aliKeys[aliDeviceId] = aliTestClient.deviceKeys;
console.log("query result will be", aliKeys);
logger.log("query result will be", aliKeys);
bobTestClient.httpBackend.when(
"POST", "/keys/query",
).respond(200, function(path, content) {
expect(content.device_keys[aliUserId]).toEqual({});
expect(content.device_keys[aliUserId]).toEqual(
[],
"Expected Bob to key query for " + aliUserId + ", got " +
Object.keys(content.device_keys),
);
const result = {};
result[aliUserId] = aliKeys;
return {device_keys: result};
@@ -154,11 +160,15 @@ function aliDownloadsKeys() {
// check that the localStorage is updated as we expect (not sure this is
// an integration test, but meh)
return Promise.all([p1, p2]).then(function() {
const devices = aliTestClient.storage.getEndToEndDevicesForUser(bobUserId);
expect(devices[bobDeviceId].keys).toEqual(bobTestClient.deviceKeys.keys);
expect(devices[bobDeviceId].verified).
toBe(0); // DeviceVerification.UNVERIFIED
return Promise.all([p1, p2]).then(() => {
return aliTestClient.client._crypto._deviceList.saveIfDirty();
}).then(() => {
aliTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
const devices = data.devices[bobUserId];
expect(devices[bobDeviceId].keys).toEqual(bobTestClient.deviceKeys.keys);
expect(devices[bobDeviceId].verified).
toBe(0); // DeviceVerification.UNVERIFIED
});
});
}
@@ -190,7 +200,7 @@ function aliSendsFirstMessage() {
expectAliQueryKeys()
.then(expectAliClaimKeys)
.then(expectAliSendMessageRequest),
]).spread(function(_, ciphertext) {
]).then(function([_, ciphertext]) {
return ciphertext;
});
}
@@ -205,7 +215,7 @@ function aliSendsMessage() {
return Promise.all([
sendMessage(aliTestClient.client),
expectAliSendMessageRequest(),
]).spread(function(_, ciphertext) {
]).then(function([_, ciphertext]) {
return ciphertext;
});
}
@@ -221,7 +231,7 @@ function bobSendsReplyMessage() {
sendMessage(bobTestClient.client),
expectBobQueryKeys()
.then(expectBobSendMessageRequest),
]).spread(function(_, ciphertext) {
]).then(function([_, ciphertext]) {
return ciphertext;
});
}
@@ -266,16 +276,17 @@ function sendMessage(client) {
function expectSendMessageRequest(httpBackend) {
const path = "/send/m.room.encrypted/";
const deferred = Promise.defer();
httpBackend.when("PUT", path).respond(200, function(path, content) {
deferred.resolve(content);
return {
event_id: "asdfgh",
};
const prom = new Promise((resolve) => {
httpBackend.when("PUT", path).respond(200, function(path, content) {
resolve(content);
return {
event_id: "asdfgh",
};
});
});
// it can take a while to process the key query
return httpBackend.flush(path, 1).then(() => deferred.promise);
return httpBackend.flush(path, 1).then(() => prom);
}
function aliRecvMessage() {
@@ -321,7 +332,7 @@ function recvMessage(httpBackend, client, sender, message) {
if (event.getType() == "m.room.member") {
return;
}
console.log(client.credentials.userId + " received event",
logger.log(client.credentials.userId + " received event",
event);
client.removeListener("event", onEvent);
@@ -387,13 +398,11 @@ function firstSync(testClient) {
describe("MatrixClient crypto", function() {
if (!sdk.CRYPTO_ENABLED) {
if (!CRYPTO_ENABLED) {
return;
}
beforeEach(async function() {
testUtils.beforeEach(this); // eslint-disable-line no-invalid-this
aliTestClient = new TestClient(aliUserId, aliDeviceId, aliAccessToken);
await aliTestClient.client.initCrypto();
@@ -405,10 +414,10 @@ describe("MatrixClient crypto", function() {
});
afterEach(function() {
aliTestClient.stop();
aliTestClient.httpBackend.verifyNoOutstandingExpectation();
bobTestClient.stop();
bobTestClient.httpBackend.verifyNoOutstandingExpectation();
return Promise.all([aliTestClient.stop(), bobTestClient.stop()]);
});
it("Bob uploads device keys", function() {
@@ -416,15 +425,14 @@ describe("MatrixClient crypto", function() {
.then(bobUploadsDeviceKeys);
});
it("Ali downloads Bobs device keys", function(done) {
Promise.resolve()
it("Ali downloads Bobs device keys", function() {
return Promise.resolve()
.then(bobUploadsDeviceKeys)
.then(aliDownloadsKeys)
.nodeify(done);
.then(aliDownloadsKeys);
});
it("Ali gets keys with an invalid signature", function(done) {
Promise.resolve()
it("Ali gets keys with an invalid signature", function() {
return Promise.resolve()
.then(bobUploadsDeviceKeys)
.then(function() {
// tamper bob's keys
@@ -441,11 +449,10 @@ describe("MatrixClient crypto", function() {
}).then((devices) => {
// should get an empty list
expect(devices).toEqual([]);
})
.nodeify(done);
});
});
it("Ali gets keys with an incorrect userId", function(done) {
it("Ali gets keys with an incorrect userId", function() {
const eveUserId = "@eve:localhost";
const bobDeviceKeys = {
@@ -474,7 +481,7 @@ describe("MatrixClient crypto", function() {
return {device_keys: result};
});
Promise.all([
return Promise.all([
aliTestClient.client.downloadKeys([bobUserId, eveUserId]),
aliTestClient.httpBackend.flush("/keys/query", 1),
]).then(function() {
@@ -482,14 +489,14 @@ describe("MatrixClient crypto", function() {
aliTestClient.client.getStoredDevicesForUser(bobUserId),
aliTestClient.client.getStoredDevicesForUser(eveUserId),
]);
}).spread((bobDevices, eveDevices) => {
}).then(([bobDevices, eveDevices]) => {
// should get an empty list
expect(bobDevices).toEqual([]);
expect(eveDevices).toEqual([]);
}).nodeify(done);
});
});
it("Ali gets keys with an incorrect deviceId", function(done) {
it("Ali gets keys with an incorrect deviceId", function() {
const bobDeviceKeys = {
algorithms: ['m.olm.v1.curve25519-aes-sha2', 'm.megolm.v1.aes-sha2'],
device_id: 'bad_device',
@@ -516,7 +523,7 @@ describe("MatrixClient crypto", function() {
return {device_keys: result};
});
Promise.all([
return Promise.all([
aliTestClient.client.downloadKeys([bobUserId]),
aliTestClient.httpBackend.flush("/keys/query", 1),
]).then(function() {
@@ -524,7 +531,7 @@ describe("MatrixClient crypto", function() {
}).then((devices) => {
// should get an empty list
expect(devices).toEqual([]);
}).nodeify(done);
});
});
@@ -534,21 +541,22 @@ describe("MatrixClient crypto", function() {
.then(() => bobTestClient.awaitOneTimeKeyUpload())
.then((keys) => {
expect(Object.keys(keys).length).toEqual(5);
expect(Object.keys(bobTestClient.deviceKeys).length).toNotEqual(0);
expect(Object.keys(bobTestClient.deviceKeys).length).not.toEqual(0);
});
});
it("Ali sends a message", function(done) {
Promise.resolve()
it("Ali sends a message", function() {
aliTestClient.expectKeyQuery({device_keys: {[aliUserId]: {}}});
return Promise.resolve()
.then(() => aliTestClient.start())
.then(() => bobTestClient.start())
.then(() => firstSync(aliTestClient))
.then(aliEnablesEncryption)
.then(aliSendsFirstMessage)
.nodeify(done);
.then(aliSendsFirstMessage);
});
it("Bob receives a message", function() {
aliTestClient.expectKeyQuery({device_keys: {[aliUserId]: {}}});
return Promise.resolve()
.then(() => aliTestClient.start())
.then(() => bobTestClient.start())
@@ -559,6 +567,7 @@ describe("MatrixClient crypto", function() {
});
it("Bob receives a message with a bogus sender", function() {
aliTestClient.expectKeyQuery({device_keys: {[aliUserId]: {}}});
return Promise.resolve()
.then(() => aliTestClient.start())
.then(() => bobTestClient.start())
@@ -591,7 +600,7 @@ describe("MatrixClient crypto", function() {
const eventPromise = new Promise((resolve, reject) => {
const onEvent = function(event) {
console.log(bobUserId + " received event",
logger.log(bobUserId + " received event",
event);
resolve(event);
};
@@ -611,8 +620,9 @@ describe("MatrixClient crypto", function() {
});
});
it("Ali blocks Bob's device", function(done) {
Promise.resolve()
it("Ali blocks Bob's device", function() {
aliTestClient.expectKeyQuery({device_keys: {[aliUserId]: {}}});
return Promise.resolve()
.then(() => aliTestClient.start())
.then(() => bobTestClient.start())
.then(() => firstSync(aliTestClient))
@@ -627,11 +637,12 @@ describe("MatrixClient crypto", function() {
expect(sentContent.ciphertext).toEqual({});
});
return Promise.all([p1, p2]);
}).nodeify(done);
});
});
it("Bob receives two pre-key messages", function(done) {
Promise.resolve()
it("Bob receives two pre-key messages", function() {
aliTestClient.expectKeyQuery({device_keys: {[aliUserId]: {}}});
return Promise.resolve()
.then(() => aliTestClient.start())
.then(() => bobTestClient.start())
.then(() => firstSync(aliTestClient))
@@ -639,11 +650,12 @@ describe("MatrixClient crypto", function() {
.then(aliSendsFirstMessage)
.then(bobRecvMessage)
.then(aliSendsMessage)
.then(bobRecvMessage)
.nodeify(done);
.then(bobRecvMessage);
});
it("Bob replies to the message", function() {
aliTestClient.expectKeyQuery({device_keys: {[aliUserId]: {}}});
bobTestClient.expectKeyQuery({device_keys: {[bobUserId]: {}}});
return Promise.resolve()
.then(() => aliTestClient.start())
.then(() => bobTestClient.start())
@@ -654,13 +666,14 @@ describe("MatrixClient crypto", function() {
.then(bobRecvMessage)
.then(bobEnablesEncryption)
.then(bobSendsReplyMessage).then(function(ciphertext) {
expect(ciphertext.type).toEqual(1);
expect(ciphertext.type).toEqual(1, "Unexpected cipghertext type.");
}).then(aliRecvMessage);
});
it("Ali does a key query when encryption is enabled", function() {
// enabling encryption in the room should make alice download devices
// for both members.
aliTestClient.expectKeyQuery({device_keys: {[aliUserId]: {}}});
return Promise.resolve()
.then(() => aliTestClient.start())
.then(() => firstSync(aliTestClient))
@@ -691,7 +704,6 @@ describe("MatrixClient crypto", function() {
}).then(() => {
aliTestClient.expectKeyQuery({
device_keys: {
[aliUserId]: {},
[bobUserId]: {},
},
});
@@ -714,7 +726,7 @@ describe("MatrixClient crypto", function() {
return Promise.resolve()
.then(() => {
console.log(aliTestClient + ': starting');
logger.log(aliTestClient + ': starting');
httpBackend.when("GET", "/pushrules").respond(200, {});
httpBackend.when("POST", "/filter").respond(200, { filter_id: "fid" });
aliTestClient.expectDeviceKeyUpload();
@@ -726,16 +738,16 @@ describe("MatrixClient crypto", function() {
aliTestClient.client.startClient({});
return httpBackend.flushAllExpected().then(() => {
console.log(aliTestClient + ': started');
logger.log(aliTestClient + ': started');
});
})
.then(() => httpBackend.when("POST", "/keys/upload")
.respond(200, (path, content) => {
expect(content.one_time_keys).toBeTruthy();
expect(content.one_time_keys).toNotEqual({});
expect(content.one_time_keys).not.toEqual({});
expect(Object.keys(content.one_time_keys).length)
.toBeGreaterThanOrEqualTo(1);
console.log('received %i one-time keys',
.toBeGreaterThanOrEqual(1);
logger.log('received %i one-time keys',
Object.keys(content.one_time_keys).length);
// cancel futher calls by telling the client
// we have more than we need
+32 -22
View File
@@ -1,28 +1,16 @@
"use strict";
import 'source-map-support/register';
const sdk = require("../..");
const HttpBackend = require("matrix-mock-request");
const utils = require("../test-utils");
import expect from 'expect';
import Promise from 'bluebird';
import * as utils from "../test-utils";
import {TestClient} from "../TestClient";
describe("MatrixClient events", function() {
const baseUrl = "http://localhost.or.something";
let client;
let httpBackend;
const selfUserId = "@alice:localhost";
const selfAccessToken = "aseukfgwef";
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this
httpBackend = new HttpBackend();
sdk.request(httpBackend.requestFn);
client = sdk.createClient({
baseUrl: baseUrl,
userId: selfUserId,
accessToken: selfAccessToken,
});
const testClient = new TestClient(selfUserId, "DEVICE", selfAccessToken);
client = testClient.client;
httpBackend = testClient.httpBackend;
httpBackend.when("GET", "/pushrules").respond(200, {});
httpBackend.when("POST", "/filter").respond(200, { filter_id: "a filter id" });
});
@@ -30,6 +18,7 @@ describe("MatrixClient events", function() {
afterEach(function() {
httpBackend.verifyNoOutstandingExpectation();
client.stopClient();
return httpBackend.stop();
});
describe("emissions", function() {
@@ -156,14 +145,14 @@ describe("MatrixClient events", function() {
return;
}
expect(event.event).toEqual(SYNC_DATA.presence.events[0]);
expect(event.event).toMatch(SYNC_DATA.presence.events[0]);
expect(user.presence).toEqual(
SYNC_DATA.presence.events[0].content.presence,
);
});
client.startClient();
httpBackend.flushAllExpected().done(function() {
httpBackend.flushAllExpected().then(function() {
expect(fired).toBe(true, "User.presence didn't fire.");
done();
});
@@ -218,7 +207,7 @@ describe("MatrixClient events", function() {
client.on("RoomState.events", function(event, state) {
eventsInvokeCount++;
const index = roomStateEventTypes.indexOf(event.getType());
expect(index).toNotEqual(
expect(index).not.toEqual(
-1, "Unexpected room state event type: " + event.getType(),
);
if (index >= 0) {
@@ -301,11 +290,32 @@ describe("MatrixClient events", function() {
});
it("should emit Session.logged_out on M_UNKNOWN_TOKEN", function() {
httpBackend.when("GET", "/sync").respond(401, { errcode: 'M_UNKNOWN_TOKEN' });
const error = { errcode: 'M_UNKNOWN_TOKEN' };
httpBackend.when("GET", "/sync").respond(401, error);
let sessionLoggedOutCount = 0;
client.on("Session.logged_out", function(event, member) {
client.on("Session.logged_out", function(errObj) {
sessionLoggedOutCount++;
expect(errObj.data).toEqual(error);
});
client.startClient();
return httpBackend.flushAllExpected().then(function() {
expect(sessionLoggedOutCount).toEqual(
1, "Session.logged_out fired wrong number of times",
);
});
});
it("should emit Session.logged_out on M_UNKNOWN_TOKEN (soft logout)", function() {
const error = { errcode: 'M_UNKNOWN_TOKEN', soft_logout: true };
httpBackend.when("GET", "/sync").respond(401, error);
let sessionLoggedOutCount = 0;
client.on("Session.logged_out", function(errObj) {
sessionLoggedOutCount++;
expect(errObj.data).toEqual(error);
});
client.startClient();
+64 -78
View File
@@ -1,12 +1,8 @@
"use strict";
import 'source-map-support/register';
import Promise from 'bluebird';
const sdk = require("../..");
const HttpBackend = require("matrix-mock-request");
const utils = require("../test-utils");
const EventTimeline = sdk.EventTimeline;
import * as utils from "../test-utils";
import {EventTimeline} from "../../src/matrix";
import {logger} from "../../src/logger";
import {TestClient} from "../TestClient";
const baseUrl = "http://localhost.or.something";
const userId = "@alice:localhost";
const userName = "Alice";
const accessToken = "aseukfgwef";
@@ -82,18 +78,19 @@ function startClient(httpBackend, client) {
client.startClient();
// set up a promise which will resolve once the client is initialised
const deferred = Promise.defer();
client.on("sync", function(state) {
console.log("sync", state);
if (state != "SYNCING") {
return;
}
deferred.resolve();
const prom = new Promise((resolve) => {
client.on("sync", function(state) {
logger.log("sync", state);
if (state != "SYNCING") {
return;
}
resolve();
});
});
return Promise.all([
httpBackend.flushAllExpected(),
deferred.promise,
prom,
]);
}
@@ -102,64 +99,56 @@ describe("getEventTimeline support", function() {
let client;
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this
httpBackend = new HttpBackend();
sdk.request(httpBackend.requestFn);
const testClient = new TestClient(userId, "DEVICE", accessToken);
client = testClient.client;
httpBackend = testClient.httpBackend;
});
afterEach(function() {
if (client) {
client.stopClient();
}
return httpBackend.stop();
});
it("timeline support must be enabled to work", function(done) {
client = sdk.createClient({
baseUrl: baseUrl,
userId: userId,
accessToken: accessToken,
});
startClient(httpBackend, client,
).then(function() {
it("timeline support must be enabled to work", function() {
return startClient(httpBackend, client).then(function() {
const room = client.getRoom(roomId);
const timelineSet = room.getTimelineSets()[0];
expect(function() {
client.getEventTimeline(timelineSet, "event");
}).toThrow();
}).nodeify(done);
});
});
it("timeline support works when enabled", function() {
client = sdk.createClient({
baseUrl: baseUrl,
userId: userId,
accessToken: accessToken,
timelineSupport: true,
});
const testClient = new TestClient(
userId,
"DEVICE",
accessToken,
undefined,
{timelineSupport: true},
);
client = testClient.client;
httpBackend = testClient.httpBackend;
return startClient(httpBackend, client).then(() => {
const room = client.getRoom(roomId);
const timelineSet = room.getTimelineSets()[0];
expect(function() {
client.getEventTimeline(timelineSet, "event");
}).toNotThrow();
}).not.toThrow();
});
});
it("scrollback should be able to scroll back to before a gappy /sync",
function(done) {
function() {
// need a client with timelineSupport disabled to make this work
client = sdk.createClient({
baseUrl: baseUrl,
userId: userId,
accessToken: accessToken,
});
let room;
startClient(httpBackend, client,
).then(function() {
return startClient(httpBackend, client).then(function() {
room = client.getRoom(roomId);
httpBackend.when("GET", "/sync").respond(200, {
@@ -215,27 +204,24 @@ describe("getEventTimeline support", function() {
expect(room.timeline[0].event).toEqual(EVENTS[0]);
expect(room.timeline[1].event).toEqual(EVENTS[1]);
expect(room.oldState.paginationToken).toEqual("pagin_end");
}).nodeify(done);
});
});
});
import expect from 'expect';
describe("MatrixClient event timelines", function() {
let client = null;
let httpBackend = null;
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this
httpBackend = new HttpBackend();
sdk.request(httpBackend.requestFn);
client = sdk.createClient({
baseUrl: baseUrl,
userId: userId,
accessToken: accessToken,
timelineSupport: true,
});
const testClient = new TestClient(
userId,
"DEVICE",
accessToken,
undefined,
{timelineSupport: true},
);
client = testClient.client;
httpBackend = testClient.httpBackend;
return startClient(httpBackend, client);
});
@@ -347,25 +333,25 @@ describe("MatrixClient event timelines", function() {
};
});
const deferred = Promise.defer();
client.on("sync", function() {
client.getEventTimeline(timelineSet, EVENTS[2].event_id,
).then(function(tl) {
expect(tl.getEvents().length).toEqual(4);
expect(tl.getEvents()[0].event).toEqual(EVENTS[1]);
expect(tl.getEvents()[1].event).toEqual(EVENTS[2]);
expect(tl.getEvents()[3].event).toEqual(EVENTS[3]);
expect(tl.getPaginationToken(EventTimeline.BACKWARDS))
.toEqual("start_token");
// expect(tl.getPaginationToken(EventTimeline.FORWARDS))
// .toEqual("s_5_4");
}).done(() => deferred.resolve(),
(e) => deferred.reject(e));
const prom = new Promise((resolve, reject) => {
client.on("sync", function() {
client.getEventTimeline(timelineSet, EVENTS[2].event_id,
).then(function(tl) {
expect(tl.getEvents().length).toEqual(4);
expect(tl.getEvents()[0].event).toEqual(EVENTS[1]);
expect(tl.getEvents()[1].event).toEqual(EVENTS[2]);
expect(tl.getEvents()[3].event).toEqual(EVENTS[3]);
expect(tl.getPaginationToken(EventTimeline.BACKWARDS))
.toEqual("start_token");
// expect(tl.getPaginationToken(EventTimeline.FORWARDS))
// .toEqual("s_5_4");
}).then(resolve, reject);
});
});
return Promise.all([
httpBackend.flushAllExpected(),
deferred.promise,
prom,
]);
});
@@ -668,11 +654,11 @@ describe("MatrixClient event timelines", function() {
// initiate the send, and set up checks to be done when it completes
// - but note that it won't complete until after the /sync does, below.
client.sendTextMessage(roomId, "a body", TXN_ID).then(function(res) {
console.log("sendTextMessage completed");
logger.log("sendTextMessage completed");
expect(res.event_id).toEqual(event.event_id);
return client.getEventTimeline(timelineSet, event.event_id);
}).then(function(tl) {
console.log("getEventTimeline completed (2)");
logger.log("getEventTimeline completed (2)");
expect(tl.getEvents().length).toEqual(2);
expect(tl.getEvents()[1].getContent().body).toEqual("a body");
}),
@@ -683,7 +669,7 @@ describe("MatrixClient event timelines", function() {
]).then(function() {
return client.getEventTimeline(timelineSet, event.event_id);
}).then(function(tl) {
console.log("getEventTimeline completed (1)");
logger.log("getEventTimeline completed (1)");
expect(tl.getEvents().length).toEqual(2);
expect(tl.getEvents()[1].event).toEqual(event);
@@ -695,7 +681,7 @@ describe("MatrixClient event timelines", function() {
});
it("should handle gappy syncs after redactions", function(done) {
it("should handle gappy syncs after redactions", function() {
// https://github.com/vector-im/vector-web/issues/1389
// a state event, followed by a redaction thereof
@@ -727,7 +713,7 @@ describe("MatrixClient event timelines", function() {
};
httpBackend.when("GET", "/sync").respond(200, syncData);
Promise.all([
return Promise.all([
httpBackend.flushAllExpected(),
utils.syncPromise(client),
]).then(function() {
@@ -763,6 +749,6 @@ describe("MatrixClient event timelines", function() {
const room = client.getRoom(roomId);
const tl = room.getLiveTimeline();
expect(tl.getEvents().length).toEqual(1);
}).nodeify(done);
});
});
});
+48 -62
View File
@@ -1,53 +1,35 @@
"use strict";
import 'source-map-support/register';
const sdk = require("../..");
const HttpBackend = require("matrix-mock-request");
const publicGlobals = require("../../lib/matrix");
const Room = publicGlobals.Room;
const MatrixInMemoryStore = publicGlobals.MatrixInMemoryStore;
const Filter = publicGlobals.Filter;
const utils = require("../test-utils");
const MockStorageApi = require("../MockStorageApi");
import expect from 'expect';
import * as utils from "../test-utils";
import {CRYPTO_ENABLED} from "../../src/client";
import {Filter, MemoryStore, Room} from "../../src/matrix";
import {TestClient} from "../TestClient";
describe("MatrixClient", function() {
const baseUrl = "http://localhost.or.something";
let client = null;
let httpBackend = null;
let store = null;
let sessionStore = null;
const userId = "@alice:localhost";
const accessToken = "aseukfgwef";
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this
httpBackend = new HttpBackend();
store = new MatrixInMemoryStore();
store = new MemoryStore();
const mockStorage = new MockStorageApi();
sessionStore = new sdk.WebStorageSessionStore(mockStorage);
sdk.request(httpBackend.requestFn);
client = sdk.createClient({
baseUrl: baseUrl,
userId: userId,
deviceId: "aliceDevice",
accessToken: accessToken,
const testClient = new TestClient(userId, "aliceDevice", accessToken, undefined, {
store: store,
sessionStore: sessionStore,
});
httpBackend = testClient.httpBackend;
client = testClient.client;
});
afterEach(function() {
httpBackend.verifyNoOutstandingExpectation();
return httpBackend.stop();
});
describe("uploadContent", function() {
const buf = new Buffer('hello world');
it("should upload the file", function(done) {
it("should upload the file", function() {
httpBackend.when(
"POST", "/_matrix/media/v1/upload",
"POST", "/_matrix/media/r0/upload",
).check(function(req) {
expect(req.rawData).toEqual(buf);
expect(req.queryParams.filename).toEqual("hi.txt");
@@ -73,25 +55,26 @@ describe("MatrixClient", function() {
expect(uploads[0].promise).toBe(prom);
expect(uploads[0].loaded).toEqual(0);
prom.then(function(response) {
const prom2 = prom.then(function(response) {
// for backwards compatibility, we return the raw JSON
expect(response).toEqual("content");
const uploads = client.getCurrentUploads();
expect(uploads.length).toEqual(0);
}).nodeify(done);
});
httpBackend.flush();
return prom2;
});
it("should parse the response if rawResponse=false", function(done) {
it("should parse the response if rawResponse=false", function() {
httpBackend.when(
"POST", "/_matrix/media/v1/upload",
"POST", "/_matrix/media/r0/upload",
).check(function(req) {
expect(req.opts.json).toBeFalsy();
}).respond(200, { "content_uri": "uri" });
client.uploadContent({
const prom = client.uploadContent({
stream: buf,
name: "hi.txt",
type: "text/plain",
@@ -99,14 +82,15 @@ describe("MatrixClient", function() {
rawResponse: false,
}).then(function(response) {
expect(response.content_uri).toEqual("uri");
}).nodeify(done);
});
httpBackend.flush();
return prom;
});
it("should parse errors into a MatrixError", function(done) {
it("should parse errors into a MatrixError", function() {
httpBackend.when(
"POST", "/_matrix/media/v1/upload",
"POST", "/_matrix/media/r0/upload",
).check(function(req) {
expect(req.rawData).toEqual(buf);
expect(req.opts.json).toBeFalsy();
@@ -115,7 +99,7 @@ describe("MatrixClient", function() {
"error": "broken",
});
client.uploadContent({
const prom = client.uploadContent({
stream: buf,
name: "hi.txt",
type: "text/plain",
@@ -125,12 +109,13 @@ describe("MatrixClient", function() {
expect(error.httpStatus).toEqual(400);
expect(error.errcode).toEqual("M_SNAFU");
expect(error.message).toEqual("broken");
}).nodeify(done);
});
httpBackend.flush();
return prom;
});
it("should return a promise which can be cancelled", function(done) {
it("should return a promise which can be cancelled", function() {
const prom = client.uploadContent({
stream: buf,
name: "hi.txt",
@@ -142,24 +127,25 @@ describe("MatrixClient", function() {
expect(uploads[0].promise).toBe(prom);
expect(uploads[0].loaded).toEqual(0);
prom.then(function(response) {
const prom2 = prom.then(function(response) {
throw Error("request not aborted");
}, function(error) {
expect(error).toEqual("aborted");
const uploads = client.getCurrentUploads();
expect(uploads.length).toEqual(0);
}).nodeify(done);
});
const r = client.cancelUpload(prom);
expect(r).toBe(true);
return prom2;
});
});
describe("joinRoom", function() {
it("should no-op if you've already joined a room", function() {
const roomId = "!foo:bar";
const room = new Room(roomId);
const room = new Room(roomId, userId);
room.addLiveEvents([
utils.mkMembership({
user: userId, room: roomId, mship: "join", event: true,
@@ -179,7 +165,7 @@ describe("MatrixClient", function() {
event_format: "client",
});
store.storeFilter(filter);
client.getFilter(userId, filterId, true).done(function(gotFilter) {
client.getFilter(userId, filterId, true).then(function(gotFilter) {
expect(gotFilter).toEqual(filter);
done();
});
@@ -200,7 +186,7 @@ describe("MatrixClient", function() {
event_format: "client",
});
store.storeFilter(storeFilter);
client.getFilter(userId, filterId, false).done(function(gotFilter) {
client.getFilter(userId, filterId, false).then(function(gotFilter) {
expect(gotFilter.getDefinition()).toEqual(httpFilterDefinition);
done();
});
@@ -218,7 +204,7 @@ describe("MatrixClient", function() {
httpBackend.when(
"GET", "/user/" + encodeURIComponent(userId) + "/filter/" + filterId,
).respond(200, httpFilterDefinition);
client.getFilter(userId, filterId, true).done(function(gotFilter) {
client.getFilter(userId, filterId, true).then(function(gotFilter) {
expect(gotFilter.getDefinition()).toEqual(httpFilterDefinition);
expect(store.getFilter(userId, filterId)).toBeTruthy();
done();
@@ -246,7 +232,7 @@ describe("MatrixClient", function() {
filter_id: filterId,
});
client.createFilter(filterDefinition).done(function(gotFilter) {
client.createFilter(filterDefinition).then(function(gotFilter) {
expect(gotFilter.getDefinition()).toEqual(filterDefinition);
expect(store.getFilter(userId, filterId)).toEqual(gotFilter);
done();
@@ -293,7 +279,7 @@ describe("MatrixClient", function() {
});
}).respond(200, response);
httpBackend.flush().done(function() {
httpBackend.flush().then(function() {
done();
});
});
@@ -301,7 +287,7 @@ describe("MatrixClient", function() {
describe("downloadKeys", function() {
if (!sdk.CRYPTO_ENABLED) {
if (!CRYPTO_ENABLED) {
return;
}
@@ -309,7 +295,7 @@ describe("MatrixClient", function() {
return client.initCrypto();
});
it("should do an HTTP request and then store the keys", function(done) {
it("should do an HTTP request and then store the keys", function() {
const ed25519key = "7wG2lzAqbjcyEkOP7O4gU7ItYcn+chKzh5sT/5r2l78";
// ed25519key = client.getDeviceEd25519Key();
const borisKeys = {
@@ -354,15 +340,15 @@ describe("MatrixClient", function() {
return client._crypto._olmDevice.sign(anotherjson.stringify(b));
};
console.log("Ed25519: " + ed25519key);
console.log("boris:", sign(borisKeys.dev1));
console.log("chaz:", sign(chazKeys.dev2));
logger.log("Ed25519: " + ed25519key);
logger.log("boris:", sign(borisKeys.dev1));
logger.log("chaz:", sign(chazKeys.dev2));
*/
httpBackend.when("POST", "/keys/query").check(function(req) {
expect(req.data).toEqual({device_keys: {
'boris': {},
'chaz': {},
'boris': [],
'chaz': [],
}});
}).respond(200, {
device_keys: {
@@ -371,7 +357,7 @@ describe("MatrixClient", function() {
},
});
client.downloadKeys(["boris", "chaz"]).then(function(res) {
const prom = client.downloadKeys(["boris", "chaz"]).then(function(res) {
assertObjectContains(res.boris.dev1, {
verified: 0, // DeviceVerification.UNVERIFIED
keys: { "ed25519:dev1": ed25519key },
@@ -385,26 +371,26 @@ describe("MatrixClient", function() {
algorithms: ["2"],
unsigned: { "ghi": "def" },
});
}).nodeify(done);
});
httpBackend.flush();
return prom;
});
});
describe("deleteDevice", function() {
const auth = {a: 1};
it("should pass through an auth dict", function(done) {
it("should pass through an auth dict", function() {
httpBackend.when(
"DELETE", "/_matrix/client/unstable/devices/my_device",
"DELETE", "/_matrix/client/r0/devices/my_device",
).check(function(req) {
expect(req.data).toEqual({auth: auth});
}).respond(200);
client.deleteDevice(
"my_device", auth,
).nodeify(done);
const prom = client.deleteDevice("my_device", auth);
httpBackend.flush();
return prom;
});
});
});
+30 -37
View File
@@ -1,12 +1,9 @@
"use strict";
import 'source-map-support/register';
const sdk = require("../..");
const MatrixClient = sdk.MatrixClient;
const HttpBackend = require("matrix-mock-request");
const utils = require("../test-utils");
import expect from 'expect';
import Promise from 'bluebird';
import * as utils from "../test-utils";
import HttpBackend from "matrix-mock-request";
import {MatrixClient} from "../../src/matrix";
import {MatrixScheduler} from "../../src/scheduler";
import {MemoryStore} from "../../src/store/memory";
import {MatrixError} from "../../src/http-api";
describe("MatrixClient opts", function() {
const baseUrl = "http://localhost.or.something";
@@ -58,12 +55,12 @@ describe("MatrixClient opts", function() {
};
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this
httpBackend = new HttpBackend();
});
afterEach(function() {
httpBackend.verifyNoOutstandingExpectation();
return httpBackend.stop();
});
describe("without opts.store", function() {
@@ -74,7 +71,7 @@ describe("MatrixClient opts", function() {
baseUrl: baseUrl,
userId: userId,
accessToken: accessToken,
scheduler: new sdk.MatrixScheduler(),
scheduler: new MatrixScheduler(),
});
});
@@ -87,20 +84,20 @@ 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();
});
httpBackend.flush("/txn1", 1);
});
it("should be able to sync / get new events", function(done) {
it("should be able to sync / get new events", async function() {
const expectedEventTypes = [ // from /initialSync
"m.room.message", "m.room.name", "m.room.member", "m.room.member",
"m.room.create",
];
client.on("event", function(event) {
expect(expectedEventTypes.indexOf(event.getType())).toNotEqual(
expect(expectedEventTypes.indexOf(event.getType())).not.toEqual(
-1, "Recv unexpected event type: " + event.getType(),
);
expectedEventTypes.splice(
@@ -110,20 +107,16 @@ describe("MatrixClient opts", function() {
httpBackend.when("GET", "/pushrules").respond(200, {});
httpBackend.when("POST", "/filter").respond(200, { filter_id: "foo" });
httpBackend.when("GET", "/sync").respond(200, syncData);
client.startClient();
httpBackend.flush("/pushrules", 1).then(function() {
return httpBackend.flush("/filter", 1);
}).then(function() {
return Promise.all([
httpBackend.flush("/sync", 1),
utils.syncPromise(client),
]);
}).done(function() {
expect(expectedEventTypes.length).toEqual(
0, "Expected to see event types: " + expectedEventTypes,
);
done();
});
await client.startClient();
await httpBackend.flush("/pushrules", 1);
await httpBackend.flush("/filter", 1);
await Promise.all([
httpBackend.flush("/sync", 1),
utils.syncPromise(client),
]);
expect(expectedEventTypes.length).toEqual(
0, "Expected to see event types: " + expectedEventTypes,
);
});
});
@@ -131,7 +124,7 @@ describe("MatrixClient opts", function() {
beforeEach(function() {
client = new MatrixClient({
request: httpBackend.requestFn,
store: new sdk.MatrixInMemoryStore(),
store: new MemoryStore(),
baseUrl: baseUrl,
userId: userId,
accessToken: accessToken,
@@ -140,11 +133,11 @@ describe("MatrixClient opts", function() {
});
it("shouldn't retry sending events", function(done) {
httpBackend.when("PUT", "/txn1").fail(500, {
httpBackend.when("PUT", "/txn1").fail(500, new MatrixError({
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");
@@ -162,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();
});
});
@@ -181,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();
});
+16 -22
View File
@@ -1,16 +1,9 @@
"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 EventStatus = sdk.EventStatus;
import expect from 'expect';
import {EventStatus} from "../../src/matrix";
import {MatrixScheduler} from "../../src/scheduler";
import {Room} from "../../src/models/room";
import {TestClient} from "../TestClient";
describe("MatrixClient retrying", function() {
const baseUrl = "http://localhost.or.something";
let client = null;
let httpBackend = null;
let scheduler;
@@ -20,22 +13,23 @@ describe("MatrixClient retrying", function() {
let room;
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this
httpBackend = new HttpBackend();
sdk.request(httpBackend.requestFn);
scheduler = new sdk.MatrixScheduler();
client = sdk.createClient({
baseUrl: baseUrl,
userId: userId,
accessToken: accessToken,
scheduler: scheduler,
});
room = new sdk.Room(roomId);
scheduler = new MatrixScheduler();
const testClient = new TestClient(
userId,
"DEVICE",
accessToken,
undefined,
{scheduler},
);
httpBackend = testClient.httpBackend;
client = testClient.client;
room = new Room(roomId);
client.store.storeRoom(room);
});
afterEach(function() {
httpBackend.verifyNoOutstandingExpectation();
return httpBackend.stop();
});
xit("should retry according to MatrixScheduler.retryFn", function() {
+28 -32
View File
@@ -1,15 +1,9 @@
"use strict";
import 'source-map-support/register';
const sdk = require("../..");
const EventStatus = sdk.EventStatus;
const HttpBackend = require("matrix-mock-request");
const utils = require("../test-utils");
import * as utils from "../test-utils";
import {EventStatus} from "../../src/models/event";
import {TestClient} from "../TestClient";
import Promise from 'bluebird';
import expect from 'expect';
describe("MatrixClient room timelines", function() {
const baseUrl = "http://localhost.or.something";
let client = null;
let httpBackend = null;
const userId = "@alice:localhost";
@@ -103,17 +97,18 @@ describe("MatrixClient room timelines", function() {
});
}
beforeEach(function(done) {
utils.beforeEach(this); // eslint-disable-line no-invalid-this
httpBackend = new HttpBackend();
sdk.request(httpBackend.requestFn);
client = sdk.createClient({
baseUrl: baseUrl,
userId: userId,
accessToken: accessToken,
// these tests should work with or without timelineSupport
timelineSupport: true,
});
beforeEach(function() {
// these tests should work with or without timelineSupport
const testClient = new TestClient(
userId,
"DEVICE",
accessToken,
undefined,
{timelineSupport: true},
);
httpBackend = testClient.httpBackend;
client = testClient.client;
setNextSyncData();
httpBackend.when("GET", "/pushrules").respond(200, {});
httpBackend.when("POST", "/filter").respond(200, { filter_id: "fid" });
@@ -122,14 +117,15 @@ describe("MatrixClient room timelines", function() {
return NEXT_SYNC_DATA;
});
client.startClient();
httpBackend.flush("/pushrules").then(function() {
return httpBackend.flush("/pushrules").then(function() {
return httpBackend.flush("/filter");
}).nodeify(done);
});
});
afterEach(function() {
httpBackend.verifyNoOutstandingExpectation();
client.stopClient();
return httpBackend.stop();
});
describe("local echo events", function() {
@@ -152,7 +148,7 @@ describe("MatrixClient room timelines", function() {
expect(member.userId).toEqual(userId);
expect(member.name).toEqual(userName);
httpBackend.flush("/sync", 1).done(function() {
httpBackend.flush("/sync", 1).then(function() {
done();
});
});
@@ -178,10 +174,10 @@ describe("MatrixClient room timelines", function() {
return;
}
const room = client.getRoom(roomId);
client.sendTextMessage(roomId, "I am a fish", "txn1").done(
client.sendTextMessage(roomId, "I am a fish", "txn1").then(
function() {
expect(room.timeline[1].getId()).toEqual(eventId);
httpBackend.flush("/sync", 1).done(function() {
httpBackend.flush("/sync", 1).then(function() {
expect(room.timeline[1].getId()).toEqual(eventId);
done();
});
@@ -211,10 +207,10 @@ describe("MatrixClient room timelines", function() {
}
const room = client.getRoom(roomId);
const promise = client.sendTextMessage(roomId, "I am a fish", "txn1");
httpBackend.flush("/sync", 1).done(function() {
httpBackend.flush("/sync", 1).then(function() {
expect(room.timeline.length).toEqual(2);
httpBackend.flush("/txn1", 1);
promise.done(function() {
promise.then(function() {
expect(room.timeline.length).toEqual(2);
expect(room.timeline[1].getId()).toEqual(eventId);
done();
@@ -249,7 +245,7 @@ describe("MatrixClient room timelines", function() {
const room = client.getRoom(roomId);
expect(room.timeline.length).toEqual(1);
client.scrollback(room).done(function() {
client.scrollback(room).then(function() {
expect(room.timeline.length).toEqual(1);
expect(room.oldState.paginationToken).toBe(null);
@@ -313,7 +309,7 @@ describe("MatrixClient room timelines", function() {
// sync response
expect(room.timeline.length).toEqual(1);
client.scrollback(room).done(function() {
client.scrollback(room).then(function() {
expect(room.timeline.length).toEqual(5);
const joinMsg = room.timeline[0];
expect(joinMsg.sender.name).toEqual("Old Alice");
@@ -351,7 +347,7 @@ describe("MatrixClient room timelines", function() {
const room = client.getRoom(roomId);
expect(room.timeline.length).toEqual(1);
client.scrollback(room).done(function() {
client.scrollback(room).then(function() {
expect(room.timeline.length).toEqual(3);
expect(room.timeline[0].event).toEqual(sbEvents[1]);
expect(room.timeline[1].event).toEqual(sbEvents[0]);
@@ -382,11 +378,11 @@ describe("MatrixClient room timelines", function() {
const room = client.getRoom(roomId);
expect(room.oldState.paginationToken).toBeTruthy();
client.scrollback(room, 1).done(function() {
client.scrollback(room, 1).then(function() {
expect(room.oldState.paginationToken).toEqual(sbEndTok);
});
httpBackend.flush("/messages", 1).done(function() {
httpBackend.flush("/messages", 1).then(function() {
// still have a sync to flush
httpBackend.flush("/sync", 1).then(() => {
done();
+46 -29
View File
@@ -1,16 +1,9 @@
"use strict";
import 'source-map-support/register';
const sdk = require("../..");
const HttpBackend = require("matrix-mock-request");
const utils = require("../test-utils");
const MatrixEvent = sdk.MatrixEvent;
const EventTimeline = sdk.EventTimeline;
import expect from 'expect';
import Promise from 'bluebird';
import {MatrixEvent} from "../../src/models/event";
import {EventTimeline} from "../../src/models/event-timeline";
import * as utils from "../test-utils";
import {TestClient} from "../TestClient";
describe("MatrixClient syncing", function() {
const baseUrl = "http://localhost.or.something";
let client = null;
let httpBackend = null;
const selfUserId = "@alice:localhost";
@@ -23,14 +16,9 @@ describe("MatrixClient syncing", function() {
const roomTwo = "!bar:localhost";
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this
httpBackend = new HttpBackend();
sdk.request(httpBackend.requestFn);
client = sdk.createClient({
baseUrl: baseUrl,
userId: selfUserId,
accessToken: selfAccessToken,
});
const testClient = new TestClient(selfUserId, "DEVICE", selfAccessToken);
httpBackend = testClient.httpBackend;
client = testClient.client;
httpBackend.when("GET", "/pushrules").respond(200, {});
httpBackend.when("POST", "/filter").respond(200, { filter_id: "a filter id" });
});
@@ -38,6 +26,7 @@ describe("MatrixClient syncing", function() {
afterEach(function() {
httpBackend.verifyNoOutstandingExpectation();
client.stopClient();
return httpBackend.stop();
});
describe("startClient", function() {
@@ -52,7 +41,7 @@ describe("MatrixClient syncing", function() {
client.startClient();
httpBackend.flushAllExpected().done(function() {
httpBackend.flushAllExpected().then(function() {
done();
});
});
@@ -66,7 +55,7 @@ describe("MatrixClient syncing", function() {
client.startClient();
httpBackend.flushAllExpected().done(function() {
httpBackend.flushAllExpected().then(function() {
done();
});
});
@@ -437,6 +426,34 @@ describe("MatrixClient syncing", function() {
});
});
// XXX: This test asserts that the js-sdk obeys the spec and treats state
// events that arrive in the incremental sync as if they preceeded the
// timeline events, however this breaks peeking, so it's disabled
// (see sync.js)
xit("should correctly interpret state in incremental sync.", function() {
httpBackend.when("GET", "/sync").respond(200, syncData);
httpBackend.when("GET", "/sync").respond(200, nextSyncData);
client.startClient();
return Promise.all([
httpBackend.flushAllExpected(),
awaitSyncEvent(2),
]).then(function() {
const room = client.getRoom(roomOne);
const stateAtStart = room.getLiveTimeline().getState(
EventTimeline.BACKWARDS,
);
const startRoomNameEvent = stateAtStart.getStateEvents('m.room.name', '');
expect(startRoomNameEvent.getContent().name).toEqual('Old room name');
const stateAtEnd = room.getLiveTimeline().getState(
EventTimeline.FORWARDS,
);
const endRoomNameEvent = stateAtEnd.getStateEvents('m.room.name', '');
expect(endRoomNameEvent.getContent().name).toEqual('A new room name');
});
});
xit("should update power levels for users in a room", function() {
});
@@ -499,7 +516,7 @@ describe("MatrixClient syncing", function() {
awaitSyncEvent(),
]).then(function() {
const room = client.getRoom(roomTwo);
expect(room).toExist();
expect(room).toBeDefined();
const tok = room.getLiveTimeline()
.getPaginationToken(EventTimeline.BACKWARDS);
expect(tok).toEqual("roomtwotok");
@@ -664,12 +681,12 @@ describe("MatrixClient syncing", function() {
include_leave: true }});
}).respond(200, { filter_id: "another_id" });
const defer = Promise.defer();
httpBackend.when("GET", "/sync").check(function(req) {
expect(req.queryParams.filter).toEqual("another_id");
defer.resolve();
}).respond(200, {});
const prom = new Promise((resolve) => {
httpBackend.when("GET", "/sync").check(function(req) {
expect(req.queryParams.filter).toEqual("another_id");
resolve();
}).respond(200, {});
});
client.syncLeftRooms();
@@ -680,7 +697,7 @@ describe("MatrixClient syncing", function() {
// flush the syncs
return httpBackend.flushAllExpected();
}),
defer.promise,
prom,
]);
});
+43 -31
View File
@@ -1,5 +1,6 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,15 +15,11 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
"use strict";
const anotherjson = require('another-json');
import Promise from 'bluebird';
import expect from 'expect';
const utils = require('../../lib/utils');
const testUtils = require('../test-utils');
const TestClient = require('../TestClient').default;
import anotherjson from "another-json";
import * as utils from "../../src/utils";
import * as testUtils from "../test-utils";
import {TestClient} from "../TestClient";
import {logger} from "../../src/logger";
const ROOM_ID = "!room:id";
@@ -203,7 +200,7 @@ function getSyncResponse(roomMembers) {
describe("megolm", function() {
if (!global.Olm) {
console.warn('not running megolm tests: Olm not present');
logger.warn('not running megolm tests: Olm not present');
return;
}
const Olm = global.Olm;
@@ -282,8 +279,6 @@ describe("megolm", function() {
}
beforeEach(async function() {
testUtils.beforeEach(this); // eslint-disable-line no-invalid-this
aliceTestClient = new TestClient(
"@alice:localhost", "xzcvb", "akjgkrgjs",
);
@@ -296,7 +291,7 @@ describe("megolm", function() {
});
afterEach(function() {
aliceTestClient.stop();
return aliceTestClient.stop();
});
it("Alice receives a megolm message", function() {
@@ -416,7 +411,7 @@ describe("megolm", function() {
return new Promise((resolve, reject) => {
event.once('Event.decrypted', (ev) => {
console.log(`${Date.now()} event ${event.getId()} now decrypted`);
logger.log(`${Date.now()} event ${event.getId()} now decrypted`);
resolve(ev);
});
});
@@ -499,6 +494,7 @@ describe("megolm", function() {
it('Alice sends a megolm message', function() {
let p2pSession;
aliceTestClient.expectKeyQuery({device_keys: {'@alice:localhost': {}}});
return aliceTestClient.start().then(() => {
// establish an olm session with alice
return createOlmSession(testOlmAccount, aliceTestClient);
@@ -554,7 +550,7 @@ describe("megolm", function() {
).respond(200, function(path, content) {
const ct = content.ciphertext;
const r = inboundGroupSession.decrypt(ct);
console.log('Decrypted received megolm message', r);
logger.log('Decrypted received megolm message', r);
expect(r.message_index).toEqual(0);
const decrypted = JSON.parse(r.plaintext);
@@ -581,6 +577,7 @@ describe("megolm", function() {
});
it("We shouldn't attempt to send to blocked devices", function() {
aliceTestClient.expectKeyQuery({device_keys: {'@alice:localhost': {}}});
return aliceTestClient.start().then(() => {
// establish an olm session with alice
return createOlmSession(testOlmAccount, aliceTestClient);
@@ -598,7 +595,7 @@ describe("megolm", function() {
return aliceTestClient.flushSync();
}).then(function() {
console.log('Forcing alice to download our device keys');
logger.log('Forcing alice to download our device keys');
aliceTestClient.httpBackend.when('POST', '/keys/query').respond(
200, getTestKeysQueryResponse('@bob:xyz'),
@@ -609,15 +606,18 @@ describe("megolm", function() {
aliceTestClient.httpBackend.flush('/keys/query', 1),
]);
}).then(function() {
console.log('Telling alice to block our device');
logger.log('Telling alice to block our device');
aliceTestClient.client.setDeviceBlocked('@bob:xyz', 'DEVICE_ID');
console.log('Telling alice to send a megolm message');
logger.log('Telling alice to send a megolm message');
aliceTestClient.httpBackend.when(
'PUT', '/send/',
).respond(200, {
event_id: '$event_id',
});
aliceTestClient.httpBackend.when(
'PUT', '/sendToDevice/org.matrix.room_key.withheld/',
).respond(200, {});
return Promise.all([
aliceTestClient.client.sendTextMessage(ROOM_ID, 'test'),
@@ -634,6 +634,7 @@ describe("megolm", function() {
let p2pSession;
let megolmSessionId;
aliceTestClient.expectKeyQuery({device_keys: {'@alice:localhost': {}}});
return aliceTestClient.start().then(() => {
// establish an olm session with alice
return createOlmSession(testOlmAccount, aliceTestClient);
@@ -653,7 +654,7 @@ describe("megolm", function() {
return aliceTestClient.flushSync();
}).then(function() {
console.log("Fetching bob's devices and marking known");
logger.log("Fetching bob's devices and marking known");
aliceTestClient.httpBackend.when('POST', '/keys/query').respond(
200, getTestKeysQueryResponse('@bob:xyz'),
@@ -666,17 +667,17 @@ describe("megolm", function() {
aliceTestClient.client.setDeviceKnown('@bob:xyz', 'DEVICE_ID');
});
}).then(function() {
console.log('Telling alice to send a megolm message');
logger.log('Telling alice to send a megolm message');
aliceTestClient.httpBackend.when(
'PUT', '/sendToDevice/m.room.encrypted/',
).respond(200, function(path, content) {
console.log('sendToDevice: ', content);
logger.log('sendToDevice: ', content);
const m = content.messages['@bob:xyz'].DEVICE_ID;
const ct = m.ciphertext[testSenderKey];
expect(ct.type).toEqual(1); // normal message
const decrypted = JSON.parse(p2pSession.decrypt(ct.type, ct.body));
console.log('decrypted sendToDevice:', decrypted);
logger.log('decrypted sendToDevice:', decrypted);
expect(decrypted.type).toEqual('m.room_key');
megolmSessionId = decrypted.content.session_id;
return {};
@@ -685,7 +686,7 @@ describe("megolm", function() {
aliceTestClient.httpBackend.when(
'PUT', '/send/',
).respond(200, function(path, content) {
console.log('/send:', content);
logger.log('/send:', content);
expect(content.session_id).toEqual(megolmSessionId);
return {
event_id: '$event_id',
@@ -701,19 +702,22 @@ describe("megolm", function() {
}),
]);
}).then(function() {
console.log('Telling alice to block our device');
logger.log('Telling alice to block our device');
aliceTestClient.client.setDeviceBlocked('@bob:xyz', 'DEVICE_ID');
console.log('Telling alice to send another megolm message');
logger.log('Telling alice to send another megolm message');
aliceTestClient.httpBackend.when(
'PUT', '/send/',
).respond(200, function(path, content) {
console.log('/send:', content);
expect(content.session_id).toNotEqual(megolmSessionId);
logger.log('/send:', content);
expect(content.session_id).not.toEqual(megolmSessionId);
return {
event_id: '$event_id',
};
});
aliceTestClient.httpBackend.when(
'PUT', '/sendToDevice/org.matrix.room_key.withheld/',
).respond(200, {});
return Promise.all([
aliceTestClient.client.sendTextMessage(ROOM_ID, 'test2'),
@@ -789,7 +793,7 @@ describe("megolm", function() {
aliceTestClient.httpBackend.when(
'PUT', '/sendToDevice/m.room.encrypted/',
).respond(200, function(path, content) {
console.log("sendToDevice: ", content);
logger.log("sendToDevice: ", content);
const m = content.messages[aliceTestClient.userId].DEVICE_ID;
const ct = m.ciphertext[testSenderKey];
expect(ct.type).toEqual(0); // pre-key message
@@ -809,7 +813,7 @@ describe("megolm", function() {
).respond(200, function(path, content) {
const ct = content.ciphertext;
const r = inboundGroupSession.decrypt(ct);
console.log('Decrypted received megolm message', r);
logger.log('Decrypted received megolm message', r);
decrypted = JSON.parse(r.plaintext);
return {
@@ -817,8 +821,14 @@ describe("megolm", function() {
};
});
// Grab the event that we'll need to resend
const room = aliceTestClient.client.getRoom(ROOM_ID);
const pendingEvents = room.getPendingEvents();
expect(pendingEvents.length).toEqual(1);
const unsentEvent = pendingEvents[0];
return Promise.all([
aliceTestClient.client.sendTextMessage(ROOM_ID, 'test'),
aliceTestClient.client.resendEvent(unsentEvent, room),
// the crypto stuff can take a while, so give the requests a whole second.
aliceTestClient.httpBackend.flushAllExpected({
@@ -837,6 +847,7 @@ describe("megolm", function() {
let downloadPromise;
let sendPromise;
aliceTestClient.expectKeyQuery({device_keys: {'@alice:localhost': {}}});
return aliceTestClient.start().then(() => {
// establish an olm session with alice
return createOlmSession(testOlmAccount, aliceTestClient);
@@ -855,7 +866,7 @@ describe("megolm", function() {
return aliceTestClient.flushSync();
}).then(function() {
// this will block
console.log('Forcing alice to download our device keys');
logger.log('Forcing alice to download our device keys');
downloadPromise = aliceTestClient.client.downloadKeys(['@bob:xyz']);
// so will this.
@@ -880,6 +891,7 @@ describe("megolm", function() {
it("Alice exports megolm keys and imports them to a new device", function() {
let messageEncrypted;
aliceTestClient.expectKeyQuery({device_keys: {'@alice:localhost': {}}});
return aliceTestClient.start().then(() => {
// establish an olm session with alice
return createOlmSession(testOlmAccount, aliceTestClient);
+14 -3
View File
@@ -1,5 +1,6 @@
/*
Copyright 2017 Vector creations Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,11 +15,21 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// try to load the olm library.
import {logger} from '../src/logger';
import * as utils from "../src/utils";
// try to load the olm library.
try {
global.Olm = require('olm');
console.log('loaded libolm');
logger.log('loaded libolm');
} catch (e) {
console.warn("unable to run crypto tests: libolm not available");
logger.warn("unable to run crypto tests: libolm not available");
}
// also try to set node crypto
try {
const crypto = require('crypto');
utils.setCrypto(crypto);
} catch (err) {
console.log('nodejs was compiled without crypto support: some tests will fail');
}
+167 -42
View File
@@ -1,12 +1,8 @@
"use strict";
import expect from 'expect';
import Promise from 'bluebird';
// load olm before the sdk if possible
import './olm-loader';
import sdk from '..';
const MatrixEvent = sdk.MatrixEvent;
import {logger} from '../src/logger';
import {MatrixEvent} from "../src/models/event";
/**
* Return a promise that is resolved when the client next emits a
@@ -15,7 +11,7 @@ const MatrixEvent = sdk.MatrixEvent;
* @param {Number=} count Number of syncs to wait for (default 1)
* @return {Promise} Resolves once the client has emitted a SYNCING event
*/
module.exports.syncPromise = function(client, count) {
export function syncPromise(client, count) {
if (count === undefined) {
count = 1;
}
@@ -25,8 +21,8 @@ module.exports.syncPromise = function(client, count) {
const p = new Promise((resolve, reject) => {
const cb = (state) => {
console.log(`${Date.now()} syncPromise(${count}): ${state}`);
if (state == 'SYNCING') {
logger.log(`${Date.now()} syncPromise(${count}): ${state}`);
if (state === 'SYNCING') {
resolve();
} else {
client.once('sync', cb);
@@ -36,21 +32,9 @@ module.exports.syncPromise = function(client, count) {
});
return p.then(() => {
return module.exports.syncPromise(client, count-1);
return syncPromise(client, count-1);
});
};
/**
* Perform common actions before each test case, e.g. printing the test case
* name to stdout.
* @param {Mocha.Context} context The test context
*/
module.exports.beforeEach = function(context) {
const desc = context.currentTest.fullTitle();
console.log(desc);
console.log(new Array(1 + desc.length).join("="));
};
}
/**
* Create a spy for an object and automatically spy its methods.
@@ -58,7 +42,7 @@ module.exports.beforeEach = function(context) {
* @param {string} name The name of the class
* @return {Object} An instantiated object with spied methods/properties.
*/
module.exports.mock = function(constr, name) {
export function mock(constr, name) {
// Based on
// http://eclipsesource.com/blogs/2014/03/27/mocks-in-jasmine-tests/
const HelperConstr = new Function(); // jshint ignore:line
@@ -70,7 +54,7 @@ module.exports.mock = function(constr, name) {
for (const key in constr.prototype) { // eslint-disable-line guard-for-in
try {
if (constr.prototype[key] instanceof Function) {
result[key] = expect.createSpy();
result[key] = jest.fn();
}
} catch (ex) {
// Direct access to some non-function fields of DOM prototypes may
@@ -79,7 +63,7 @@ module.exports.mock = function(constr, name) {
}
}
return result;
};
}
/**
* Create an Event.
@@ -92,7 +76,7 @@ module.exports.mock = function(constr, name) {
* @param {boolean} opts.event True to make a MatrixEvent.
* @return {Object} a JSON object representing this event.
*/
module.exports.mkEvent = function(opts) {
export function mkEvent(opts) {
if (!opts.type || !opts.content) {
throw new Error("Missing .type or .content =>" + JSON.stringify(opts));
}
@@ -111,14 +95,14 @@ module.exports.mkEvent = function(opts) {
event.state_key = "";
}
return opts.event ? new MatrixEvent(event) : event;
};
}
/**
* Create an m.presence event.
* @param {Object} opts Values for the presence.
* @return {Object|MatrixEvent} The event
*/
module.exports.mkPresence = function(opts) {
export function mkPresence(opts) {
if (!opts.user) {
throw new Error("Missing user");
}
@@ -134,7 +118,7 @@ module.exports.mkPresence = function(opts) {
},
};
return opts.event ? new MatrixEvent(event) : event;
};
}
/**
* Create an m.room.member event.
@@ -149,7 +133,7 @@ module.exports.mkPresence = function(opts) {
* @param {boolean} opts.event True to make a MatrixEvent.
* @return {Object|MatrixEvent} The event
*/
module.exports.mkMembership = function(opts) {
export function mkMembership(opts) {
opts.type = "m.room.member";
if (!opts.skey) {
opts.skey = opts.sender || opts.user;
@@ -166,8 +150,8 @@ module.exports.mkMembership = function(opts) {
if (opts.url) {
opts.content.avatar_url = opts.url;
}
return module.exports.mkEvent(opts);
};
return mkEvent(opts);
}
/**
* Create an m.room.message event.
@@ -178,7 +162,7 @@ module.exports.mkMembership = function(opts) {
* @param {boolean} opts.event True to make a MatrixEvent.
* @return {Object|MatrixEvent} The event
*/
module.exports.mkMessage = function(opts) {
export function mkMessage(opts) {
opts.type = "m.room.message";
if (!opts.msg) {
opts.msg = "Random->" + Math.random();
@@ -190,8 +174,8 @@ module.exports.mkMessage = function(opts) {
msgtype: "m.text",
body: opts.msg,
};
return module.exports.mkEvent(opts);
};
return mkEvent(opts);
}
/**
@@ -199,10 +183,10 @@ module.exports.mkMessage = function(opts) {
*
* @constructor
*/
module.exports.MockStorageApi = function() {
export function MockStorageApi() {
this.data = {};
};
module.exports.MockStorageApi.prototype = {
}
MockStorageApi.prototype = {
get length() {
return Object.keys(this.data).length;
},
@@ -227,17 +211,158 @@ module.exports.MockStorageApi.prototype = {
* @param {MatrixEvent} event
* @returns {Promise} promise which resolves (to `event`) when the event has been decrypted
*/
module.exports.awaitDecryption = function(event) {
export function awaitDecryption(event) {
if (!event.isBeingDecrypted()) {
return Promise.resolve(event);
}
console.log(`${Date.now()} event ${event.getId()} is being decrypted; waiting`);
logger.log(`${Date.now()} event ${event.getId()} is being decrypted; waiting`);
return new Promise((resolve, reject) => {
event.once('Event.decrypted', (ev) => {
console.log(`${Date.now()} event ${event.getId()} now decrypted`);
logger.log(`${Date.now()} event ${event.getId()} now decrypted`);
resolve(ev);
});
});
}
export function HttpResponse(
httpLookups, acceptKeepalives, ignoreUnhandledSync,
) {
this.httpLookups = httpLookups;
this.acceptKeepalives = acceptKeepalives === undefined ? true : acceptKeepalives;
this.ignoreUnhandledSync = ignoreUnhandledSync;
this.pendingLookup = null;
}
HttpResponse.prototype.request = function(
cb, method, path, qp, data, prefix,
) {
if (path === HttpResponse.KEEP_ALIVE_PATH && this.acceptKeepalives) {
return Promise.resolve();
}
const next = this.httpLookups.shift();
const logLine = (
"MatrixClient[UT] RECV " + method + " " + path + " " +
"EXPECT " + (next ? next.method : next) + " " + (next ? next.path : next)
);
logger.log(logLine);
if (!next) { // no more things to return
if (method === "GET" && path === "/sync" && this.ignoreUnhandledSync) {
logger.log("MatrixClient[UT] Ignoring.");
return new Promise(() => {});
}
if (this.pendingLookup) {
if (this.pendingLookup.method === method
&& this.pendingLookup.path === path) {
return this.pendingLookup.promise;
}
// >1 pending thing, and they are different, whine.
expect(false).toBe(
true, ">1 pending request. You should probably handle them. " +
"PENDING: " + JSON.stringify(this.pendingLookup) + " JUST GOT: " +
method + " " + path,
);
}
this.pendingLookup = {
promise: new Promise(() => {}),
method: method,
path: path,
};
return this.pendingLookup.promise;
}
if (next.path === path && next.method === method) {
logger.log(
"MatrixClient[UT] Matched. Returning " +
(next.error ? "BAD" : "GOOD") + " response",
);
if (next.expectBody) {
expect(next.expectBody).toEqual(data);
}
if (next.expectQueryParams) {
Object.keys(next.expectQueryParams).forEach(function(k) {
expect(qp[k]).toEqual(next.expectQueryParams[k]);
});
}
if (next.thenCall) {
process.nextTick(next.thenCall, 0); // next tick so we return first.
}
if (next.error) {
return Promise.reject({
errcode: next.error.errcode,
httpStatus: next.error.httpStatus,
name: next.error.errcode,
message: "Expected testing error",
data: next.error,
});
}
return Promise.resolve(next.data);
} else if (method === "GET" && path === "/sync" && this.ignoreUnhandledSync) {
logger.log("MatrixClient[UT] Ignoring.");
this.httpLookups.unshift(next);
return new Promise(() => {});
}
expect(true).toBe(false, "Expected different request. " + logLine);
return new Promise(() => {});
};
HttpResponse.KEEP_ALIVE_PATH = "/_matrix/client/versions";
HttpResponse.PUSH_RULES_RESPONSE = {
method: "GET",
path: "/pushrules/",
data: {},
};
HttpResponse.USER_ID = "@alice:bar";
HttpResponse.filterResponse = function(userId) {
const filterPath = "/user/" + encodeURIComponent(userId) + "/filter";
return {
method: "POST",
path: filterPath,
data: { filter_id: "f1lt3r" },
};
};
HttpResponse.SYNC_DATA = {
next_batch: "s_5_3",
presence: { events: [] },
rooms: {},
};
HttpResponse.SYNC_RESPONSE = {
method: "GET",
path: "/sync",
data: HttpResponse.SYNC_DATA,
};
HttpResponse.defaultResponses = function(userId) {
return [
HttpResponse.PUSH_RULES_RESPONSE,
HttpResponse.filterResponse(userId),
HttpResponse.SYNC_RESPONSE,
];
};
export function setHttpResponses(
client, responses, acceptKeepalives, ignoreUnhandledSyncs,
) {
const httpResponseObj = new HttpResponse(
responses, acceptKeepalives, ignoreUnhandledSyncs,
);
const httpReq = httpResponseObj.request.bind(httpResponseObj);
client._http = [
"authedRequest", "authedRequestWithPrefix", "getContentUri",
"request", "requestWithPrefix", "uploadContent",
].reduce((r, k) => {r[k] = jest.fn(); return r;}, {});
client._http.authedRequest.mockImplementation(httpReq);
client._http.authedRequestWithPrefix.mockImplementation(httpReq);
client._http.requestWithPrefix.mockImplementation(httpReq);
client._http.request.mockImplementation(httpReq);
}
+662
View File
@@ -0,0 +1,662 @@
/*
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 MockHttpBackend from "matrix-mock-request";
import * as sdk from "../../src";
import {AutoDiscovery} from "../../src/autodiscovery";
describe("AutoDiscovery", function() {
let httpBackend = null;
beforeEach(function() {
httpBackend = new MockHttpBackend();
sdk.request(httpBackend.requestFn);
});
it("should throw an error when no domain is specified", function() {
return Promise.all([
AutoDiscovery.findClientConfig(/* no args */).then(() => {
throw new Error("Expected a failure, not success with no args");
}, () => {
return true;
}),
AutoDiscovery.findClientConfig("").then(() => {
throw new Error("Expected a failure, not success with an empty string");
}, () => {
return true;
}),
AutoDiscovery.findClientConfig(null).then(() => {
throw new Error("Expected a failure, not success with null");
}, () => {
return true;
}),
AutoDiscovery.findClientConfig(true).then(() => {
throw new Error("Expected a failure, not success with a non-string");
}, () => {
return true;
}),
]);
});
it("should return PROMPT when .well-known 404s", function() {
httpBackend.when("GET", "/.well-known/matrix/client").respond(404, {});
return Promise.all([
httpBackend.flushAllExpected(),
AutoDiscovery.findClientConfig("example.org").then((conf) => {
const expected = {
"m.homeserver": {
state: "PROMPT",
error: null,
base_url: null,
},
"m.identity_server": {
state: "PROMPT",
error: null,
base_url: null,
},
};
expect(conf).toEqual(expected);
}),
]);
});
it("should return FAIL_PROMPT when .well-known returns a 500 error", function() {
httpBackend.when("GET", "/.well-known/matrix/client").respond(500, {});
return Promise.all([
httpBackend.flushAllExpected(),
AutoDiscovery.findClientConfig("example.org").then((conf) => {
const expected = {
"m.homeserver": {
state: "FAIL_PROMPT",
error: AutoDiscovery.ERROR_INVALID,
base_url: null,
},
"m.identity_server": {
state: "PROMPT",
error: null,
base_url: null,
},
};
expect(conf).toEqual(expected);
}),
]);
});
it("should return FAIL_PROMPT when .well-known returns a 400 error", function() {
httpBackend.when("GET", "/.well-known/matrix/client").respond(400, {});
return Promise.all([
httpBackend.flushAllExpected(),
AutoDiscovery.findClientConfig("example.org").then((conf) => {
const expected = {
"m.homeserver": {
state: "FAIL_PROMPT",
error: AutoDiscovery.ERROR_INVALID,
base_url: null,
},
"m.identity_server": {
state: "PROMPT",
error: null,
base_url: null,
},
};
expect(conf).toEqual(expected);
}),
]);
});
it("should return FAIL_PROMPT when .well-known returns an empty body", function() {
httpBackend.when("GET", "/.well-known/matrix/client").respond(200, "");
return Promise.all([
httpBackend.flushAllExpected(),
AutoDiscovery.findClientConfig("example.org").then((conf) => {
const expected = {
"m.homeserver": {
state: "FAIL_PROMPT",
error: AutoDiscovery.ERROR_INVALID,
base_url: null,
},
"m.identity_server": {
state: "PROMPT",
error: null,
base_url: null,
},
};
expect(conf).toEqual(expected);
}),
]);
});
it("should return FAIL_PROMPT when .well-known returns not-JSON", function() {
httpBackend.when("GET", "/.well-known/matrix/client").respond(200, "abc");
return Promise.all([
httpBackend.flushAllExpected(),
AutoDiscovery.findClientConfig("example.org").then((conf) => {
const expected = {
"m.homeserver": {
state: "FAIL_PROMPT",
error: AutoDiscovery.ERROR_INVALID,
base_url: null,
},
"m.identity_server": {
state: "PROMPT",
error: null,
base_url: null,
},
};
expect(conf).toEqual(expected);
}),
]);
});
it("should return FAIL_PROMPT when .well-known does not have a base_url for " +
"m.homeserver (empty string)", function() {
httpBackend.when("GET", "/.well-known/matrix/client").respond(200, {
"m.homeserver": {
base_url: "",
},
});
return Promise.all([
httpBackend.flushAllExpected(),
AutoDiscovery.findClientConfig("example.org").then((conf) => {
const expected = {
"m.homeserver": {
state: "FAIL_PROMPT",
error: AutoDiscovery.ERROR_INVALID_HS_BASE_URL,
base_url: null,
},
"m.identity_server": {
state: "PROMPT",
error: null,
base_url: null,
},
};
expect(conf).toEqual(expected);
}),
]);
});
it("should return FAIL_PROMPT when .well-known does not have a base_url for " +
"m.homeserver (no property)", function() {
httpBackend.when("GET", "/.well-known/matrix/client").respond(200, {
"m.homeserver": {},
});
return Promise.all([
httpBackend.flushAllExpected(),
AutoDiscovery.findClientConfig("example.org").then((conf) => {
const expected = {
"m.homeserver": {
state: "FAIL_PROMPT",
error: AutoDiscovery.ERROR_INVALID_HS_BASE_URL,
base_url: null,
},
"m.identity_server": {
state: "PROMPT",
error: null,
base_url: null,
},
};
expect(conf).toEqual(expected);
}),
]);
});
it("should return FAIL_ERROR when .well-known has an invalid base_url for " +
"m.homeserver (disallowed scheme)", function() {
httpBackend.when("GET", "/.well-known/matrix/client").respond(200, {
"m.homeserver": {
base_url: "mxc://example.org",
},
});
return Promise.all([
httpBackend.flushAllExpected(),
AutoDiscovery.findClientConfig("example.org").then((conf) => {
const expected = {
"m.homeserver": {
state: "FAIL_ERROR",
error: AutoDiscovery.ERROR_INVALID_HS_BASE_URL,
base_url: null,
},
"m.identity_server": {
state: "PROMPT",
error: null,
base_url: null,
},
};
expect(conf).toEqual(expected);
}),
]);
});
it("should return FAIL_ERROR when .well-known has an invalid base_url for " +
"m.homeserver (verification failure: 404)", function() {
httpBackend.when("GET", "/_matrix/client/versions").respond(404, {});
httpBackend.when("GET", "/.well-known/matrix/client").respond(200, {
"m.homeserver": {
base_url: "https://example.org",
},
});
return Promise.all([
httpBackend.flushAllExpected(),
AutoDiscovery.findClientConfig("example.org").then((conf) => {
const expected = {
"m.homeserver": {
state: "FAIL_ERROR",
error: AutoDiscovery.ERROR_INVALID_HOMESERVER,
base_url: "https://example.org",
},
"m.identity_server": {
state: "PROMPT",
error: null,
base_url: null,
},
};
expect(conf).toEqual(expected);
}),
]);
});
it("should return FAIL_ERROR when .well-known has an invalid base_url for " +
"m.homeserver (verification failure: 500)", function() {
httpBackend.when("GET", "/_matrix/client/versions").respond(500, {});
httpBackend.when("GET", "/.well-known/matrix/client").respond(200, {
"m.homeserver": {
base_url: "https://example.org",
},
});
return Promise.all([
httpBackend.flushAllExpected(),
AutoDiscovery.findClientConfig("example.org").then((conf) => {
const expected = {
"m.homeserver": {
state: "FAIL_ERROR",
error: AutoDiscovery.ERROR_INVALID_HOMESERVER,
base_url: "https://example.org",
},
"m.identity_server": {
state: "PROMPT",
error: null,
base_url: null,
},
};
expect(conf).toEqual(expected);
}),
]);
});
it("should return FAIL_ERROR when .well-known has an invalid base_url for " +
"m.homeserver (verification failure: 200 but wrong content)", function() {
httpBackend.when("GET", "/_matrix/client/versions").respond(200, {
not_matrix_versions: ["r0.0.1"],
});
httpBackend.when("GET", "/.well-known/matrix/client").respond(200, {
"m.homeserver": {
base_url: "https://example.org",
},
});
return Promise.all([
httpBackend.flushAllExpected(),
AutoDiscovery.findClientConfig("example.org").then((conf) => {
const expected = {
"m.homeserver": {
state: "FAIL_ERROR",
error: AutoDiscovery.ERROR_INVALID_HOMESERVER,
base_url: "https://example.org",
},
"m.identity_server": {
state: "PROMPT",
error: null,
base_url: null,
},
};
expect(conf).toEqual(expected);
}),
]);
});
it("should return SUCCESS when .well-known has a verifiably accurate base_url for " +
"m.homeserver", function() {
httpBackend.when("GET", "/_matrix/client/versions").check((req) => {
expect(req.opts.uri).toEqual("https://example.org/_matrix/client/versions");
}).respond(200, {
versions: ["r0.0.1"],
});
httpBackend.when("GET", "/.well-known/matrix/client").respond(200, {
"m.homeserver": {
base_url: "https://example.org",
},
});
return Promise.all([
httpBackend.flushAllExpected(),
AutoDiscovery.findClientConfig("example.org").then((conf) => {
const expected = {
"m.homeserver": {
state: "SUCCESS",
error: null,
base_url: "https://example.org",
},
"m.identity_server": {
state: "PROMPT",
error: null,
base_url: null,
},
};
expect(conf).toEqual(expected);
}),
]);
});
it("should return SUCCESS with the right homeserver URL", function() {
httpBackend.when("GET", "/_matrix/client/versions").check((req) => {
expect(req.opts.uri)
.toEqual("https://chat.example.org/_matrix/client/versions");
}).respond(200, {
versions: ["r0.0.1"],
});
httpBackend.when("GET", "/.well-known/matrix/client").respond(200, {
"m.homeserver": {
// Note: we also expect this test to trim the trailing slash
base_url: "https://chat.example.org/",
},
});
return Promise.all([
httpBackend.flushAllExpected(),
AutoDiscovery.findClientConfig("example.org").then((conf) => {
const expected = {
"m.homeserver": {
state: "SUCCESS",
error: null,
base_url: "https://chat.example.org",
},
"m.identity_server": {
state: "PROMPT",
error: null,
base_url: null,
},
};
expect(conf).toEqual(expected);
}),
]);
});
it("should return SUCCESS / FAIL_PROMPT when the identity server configuration " +
"is wrong (missing base_url)", function() {
httpBackend.when("GET", "/_matrix/client/versions").check((req) => {
expect(req.opts.uri)
.toEqual("https://chat.example.org/_matrix/client/versions");
}).respond(200, {
versions: ["r0.0.1"],
});
httpBackend.when("GET", "/.well-known/matrix/client").respond(200, {
"m.homeserver": {
// Note: we also expect this test to trim the trailing slash
base_url: "https://chat.example.org/",
},
"m.identity_server": {
not_base_url: "https://identity.example.org",
},
});
return Promise.all([
httpBackend.flushAllExpected(),
AutoDiscovery.findClientConfig("example.org").then((conf) => {
const expected = {
"m.homeserver": {
state: "SUCCESS",
error: null,
// We still expect the base_url to be here for debugging purposes.
base_url: "https://chat.example.org",
},
"m.identity_server": {
state: "FAIL_PROMPT",
error: AutoDiscovery.ERROR_INVALID_IS_BASE_URL,
base_url: null,
},
};
expect(conf).toEqual(expected);
}),
]);
});
it("should return SUCCESS / FAIL_PROMPT when the identity server configuration " +
"is wrong (empty base_url)", function() {
httpBackend.when("GET", "/_matrix/client/versions").check((req) => {
expect(req.opts.uri)
.toEqual("https://chat.example.org/_matrix/client/versions");
}).respond(200, {
versions: ["r0.0.1"],
});
httpBackend.when("GET", "/.well-known/matrix/client").respond(200, {
"m.homeserver": {
// Note: we also expect this test to trim the trailing slash
base_url: "https://chat.example.org/",
},
"m.identity_server": {
base_url: "",
},
});
return Promise.all([
httpBackend.flushAllExpected(),
AutoDiscovery.findClientConfig("example.org").then((conf) => {
const expected = {
"m.homeserver": {
state: "SUCCESS",
error: null,
// We still expect the base_url to be here for debugging purposes.
base_url: "https://chat.example.org",
},
"m.identity_server": {
state: "FAIL_PROMPT",
error: AutoDiscovery.ERROR_INVALID_IS_BASE_URL,
base_url: null,
},
};
expect(conf).toEqual(expected);
}),
]);
});
it("should return SUCCESS / FAIL_PROMPT when the identity server configuration " +
"is wrong (validation error: 404)", function() {
httpBackend.when("GET", "/_matrix/client/versions").check((req) => {
expect(req.opts.uri)
.toEqual("https://chat.example.org/_matrix/client/versions");
}).respond(200, {
versions: ["r0.0.1"],
});
httpBackend.when("GET", "/_matrix/identity/api/v1").respond(404, {});
httpBackend.when("GET", "/.well-known/matrix/client").respond(200, {
"m.homeserver": {
// Note: we also expect this test to trim the trailing slash
base_url: "https://chat.example.org/",
},
"m.identity_server": {
base_url: "https://identity.example.org",
},
});
return Promise.all([
httpBackend.flushAllExpected(),
AutoDiscovery.findClientConfig("example.org").then((conf) => {
const expected = {
"m.homeserver": {
state: "SUCCESS",
error: null,
// We still expect the base_url to be here for debugging purposes.
base_url: "https://chat.example.org",
},
"m.identity_server": {
state: "FAIL_PROMPT",
error: AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER,
base_url: "https://identity.example.org",
},
};
expect(conf).toEqual(expected);
}),
]);
});
it("should return SUCCESS / FAIL_PROMPT when the identity server configuration " +
"is wrong (validation error: 500)", function() {
httpBackend.when("GET", "/_matrix/client/versions").check((req) => {
expect(req.opts.uri)
.toEqual("https://chat.example.org/_matrix/client/versions");
}).respond(200, {
versions: ["r0.0.1"],
});
httpBackend.when("GET", "/_matrix/identity/api/v1").respond(500, {});
httpBackend.when("GET", "/.well-known/matrix/client").respond(200, {
"m.homeserver": {
// Note: we also expect this test to trim the trailing slash
base_url: "https://chat.example.org/",
},
"m.identity_server": {
base_url: "https://identity.example.org",
},
});
return Promise.all([
httpBackend.flushAllExpected(),
AutoDiscovery.findClientConfig("example.org").then((conf) => {
const expected = {
"m.homeserver": {
state: "SUCCESS",
error: null,
// We still expect the base_url to be here for debugging purposes
base_url: "https://chat.example.org",
},
"m.identity_server": {
state: "FAIL_PROMPT",
error: AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER,
base_url: "https://identity.example.org",
},
};
expect(conf).toEqual(expected);
}),
]);
});
it("should return SUCCESS when the identity server configuration is " +
"verifiably accurate", function() {
httpBackend.when("GET", "/_matrix/client/versions").check((req) => {
expect(req.opts.uri)
.toEqual("https://chat.example.org/_matrix/client/versions");
}).respond(200, {
versions: ["r0.0.1"],
});
httpBackend.when("GET", "/_matrix/identity/api/v1").check((req) => {
expect(req.opts.uri)
.toEqual("https://identity.example.org/_matrix/identity/api/v1");
}).respond(200, {});
httpBackend.when("GET", "/.well-known/matrix/client").respond(200, {
"m.homeserver": {
// Note: we also expect this test to trim the trailing slash
base_url: "https://chat.example.org/",
},
"m.identity_server": {
base_url: "https://identity.example.org",
},
});
return Promise.all([
httpBackend.flushAllExpected(),
AutoDiscovery.findClientConfig("example.org").then((conf) => {
const expected = {
"m.homeserver": {
state: "SUCCESS",
error: null,
base_url: "https://chat.example.org",
},
"m.identity_server": {
state: "SUCCESS",
error: null,
base_url: "https://identity.example.org",
},
};
expect(conf).toEqual(expected);
}),
]);
});
it("should return SUCCESS and preserve non-standard keys from the " +
".well-known response", function() {
httpBackend.when("GET", "/_matrix/client/versions").check((req) => {
expect(req.opts.uri)
.toEqual("https://chat.example.org/_matrix/client/versions");
}).respond(200, {
versions: ["r0.0.1"],
});
httpBackend.when("GET", "/_matrix/identity/api/v1").check((req) => {
expect(req.opts.uri)
.toEqual("https://identity.example.org/_matrix/identity/api/v1");
}).respond(200, {});
httpBackend.when("GET", "/.well-known/matrix/client").respond(200, {
"m.homeserver": {
// Note: we also expect this test to trim the trailing slash
base_url: "https://chat.example.org/",
},
"m.identity_server": {
base_url: "https://identity.example.org",
},
"org.example.custom.property": {
cupcakes: "yes",
},
});
return Promise.all([
httpBackend.flushAllExpected(),
AutoDiscovery.findClientConfig("example.org").then((conf) => {
const expected = {
"m.homeserver": {
state: "SUCCESS",
error: null,
base_url: "https://chat.example.org",
},
"m.identity_server": {
state: "SUCCESS",
error: null,
base_url: "https://identity.example.org",
},
"org.example.custom.property": {
cupcakes: "yes",
},
};
expect(conf).toEqual(expected);
}),
]);
});
});
+19 -28
View File
@@ -1,22 +1,13 @@
"use strict";
import 'source-map-support/register';
const ContentRepo = require("../../lib/content-repo");
const testUtils = require("../test-utils");
import expect from 'expect';
import {getHttpUriForMxc, getIdenticonUri} from "../../src/content-repo";
describe("ContentRepo", function() {
const baseUrl = "https://my.home.server";
beforeEach(function() {
testUtils.beforeEach(this); // eslint-disable-line no-invalid-this
});
describe("getHttpUriForMxc", function() {
it("should do nothing to HTTP URLs when allowing direct links", function() {
const httpUrl = "http://example.com/image.jpeg";
expect(
ContentRepo.getHttpUriForMxc(
getHttpUriForMxc(
baseUrl, httpUrl, undefined, undefined, undefined, true,
),
).toEqual(httpUrl);
@@ -24,26 +15,26 @@ describe("ContentRepo", function() {
it("should return the empty string HTTP URLs by default", function() {
const httpUrl = "http://example.com/image.jpeg";
expect(ContentRepo.getHttpUriForMxc(baseUrl, httpUrl)).toEqual("");
expect(getHttpUriForMxc(baseUrl, httpUrl)).toEqual("");
});
it("should return a download URL if no width/height/resize are specified",
function() {
const mxcUri = "mxc://server.name/resourceid";
expect(ContentRepo.getHttpUriForMxc(baseUrl, mxcUri)).toEqual(
baseUrl + "/_matrix/media/v1/download/server.name/resourceid",
expect(getHttpUriForMxc(baseUrl, mxcUri)).toEqual(
baseUrl + "/_matrix/media/r0/download/server.name/resourceid",
);
});
it("should return the empty string for null input", function() {
expect(ContentRepo.getHttpUriForMxc(null)).toEqual("");
expect(getHttpUriForMxc(null)).toEqual("");
});
it("should return a thumbnail URL if a width/height/resize is specified",
function() {
const mxcUri = "mxc://server.name/resourceid";
expect(ContentRepo.getHttpUriForMxc(baseUrl, mxcUri, 32, 64, "crop")).toEqual(
baseUrl + "/_matrix/media/v1/thumbnail/server.name/resourceid" +
expect(getHttpUriForMxc(baseUrl, mxcUri, 32, 64, "crop")).toEqual(
baseUrl + "/_matrix/media/r0/thumbnail/server.name/resourceid" +
"?width=32&height=64&method=crop",
);
});
@@ -51,8 +42,8 @@ describe("ContentRepo", function() {
it("should put fragments from mxc:// URIs after any query parameters",
function() {
const mxcUri = "mxc://server.name/resourceid#automade";
expect(ContentRepo.getHttpUriForMxc(baseUrl, mxcUri, 32)).toEqual(
baseUrl + "/_matrix/media/v1/thumbnail/server.name/resourceid" +
expect(getHttpUriForMxc(baseUrl, mxcUri, 32)).toEqual(
baseUrl + "/_matrix/media/r0/thumbnail/server.name/resourceid" +
"?width=32#automade",
);
});
@@ -60,34 +51,34 @@ describe("ContentRepo", function() {
it("should put fragments from mxc:// URIs at the end of the HTTP URI",
function() {
const mxcUri = "mxc://server.name/resourceid#automade";
expect(ContentRepo.getHttpUriForMxc(baseUrl, mxcUri)).toEqual(
baseUrl + "/_matrix/media/v1/download/server.name/resourceid#automade",
expect(getHttpUriForMxc(baseUrl, mxcUri)).toEqual(
baseUrl + "/_matrix/media/r0/download/server.name/resourceid#automade",
);
});
});
describe("getIdenticonUri", function() {
it("should do nothing for null input", function() {
expect(ContentRepo.getIdenticonUri(null)).toEqual(null);
expect(getIdenticonUri(null)).toEqual(null);
});
it("should set w/h by default to 96", function() {
expect(ContentRepo.getIdenticonUri(baseUrl, "foobar")).toEqual(
baseUrl + "/_matrix/media/v1/identicon/foobar" +
expect(getIdenticonUri(baseUrl, "foobar")).toEqual(
baseUrl + "/_matrix/media/unstable/identicon/foobar" +
"?width=96&height=96",
);
});
it("should be able to set custom w/h", function() {
expect(ContentRepo.getIdenticonUri(baseUrl, "foobar", 32, 64)).toEqual(
baseUrl + "/_matrix/media/v1/identicon/foobar" +
expect(getIdenticonUri(baseUrl, "foobar", 32, 64)).toEqual(
baseUrl + "/_matrix/media/unstable/identicon/foobar" +
"?width=32&height=64",
);
});
it("should URL encode the identicon string", function() {
expect(ContentRepo.getIdenticonUri(baseUrl, "foo#bar", 32, 64)).toEqual(
baseUrl + "/_matrix/media/v1/identicon/foo%23bar" +
expect(getIdenticonUri(baseUrl, "foo#bar", 32, 64)).toEqual(
baseUrl + "/_matrix/media/unstable/identicon/foo%23bar" +
"?width=32&height=64",
);
});
+332 -12
View File
@@ -1,20 +1,340 @@
import '../olm-loader';
import {Crypto} from "../../src/crypto";
import {WebStorageSessionStore} from "../../src/store/session/webstorage";
import {MemoryCryptoStore} from "../../src/crypto/store/memory-crypto-store";
import {MockStorageApi} from "../MockStorageApi";
import {TestClient} from "../TestClient";
import {MatrixEvent} from "../../src/models/event";
import {Room} from "../../src/models/room";
import * as olmlib from "../../src/crypto/olmlib";
import {sleep} from "../../src/utils";
import {EventEmitter} from "events";
import {CRYPTO_ENABLED} from "../../src/client";
"use strict";
import 'source-map-support/register';
const sdk = require("../..");
let Crypto;
if (sdk.CRYPTO_ENABLED) {
Crypto = require("../../lib/crypto");
}
import expect from 'expect';
const Olm = global.Olm;
describe("Crypto", function() {
if (!sdk.CRYPTO_ENABLED) {
if (!CRYPTO_ENABLED) {
return;
}
beforeAll(function() {
return Olm.init();
});
it("Crypto exposes the correct olm library version", function() {
expect(Crypto.getOlmVersion()[0]).toEqual(2);
expect(Crypto.getOlmVersion()[0]).toEqual(3);
});
describe('Session management', function() {
const otkResponse = {
one_time_keys: {
'@alice:home.server': {
aliceDevice: {
'signed_curve25519:FLIBBLE': {
key: 'YmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmI',
signatures: {
'@alice:home.server': {
'ed25519:aliceDevice': 'totally a valid signature',
},
},
},
},
},
},
};
let crypto;
let mockBaseApis;
let mockRoomList;
let fakeEmitter;
beforeEach(async function() {
const mockStorage = new MockStorageApi();
const sessionStore = new WebStorageSessionStore(mockStorage);
const cryptoStore = new MemoryCryptoStore(mockStorage);
cryptoStore.storeEndToEndDeviceData({
devices: {
'@bob:home.server': {
'BOBDEVICE': {
keys: {
'curve25519:BOBDEVICE': 'this is a key',
},
},
},
},
trackingStatus: {},
});
mockBaseApis = {
sendToDevice: jest.fn(),
getKeyBackupVersion: jest.fn(),
isGuest: jest.fn(),
};
mockRoomList = {};
fakeEmitter = new EventEmitter();
crypto = new Crypto(
mockBaseApis,
sessionStore,
"@alice:home.server",
"FLIBBLE",
sessionStore,
cryptoStore,
mockRoomList,
);
crypto.registerEventHandlers(fakeEmitter);
await crypto.init();
});
afterEach(async function() {
await crypto.stop();
});
it("restarts wedged Olm sessions", async function() {
const prom = new Promise((resolve) => {
mockBaseApis.claimOneTimeKeys = function() {
resolve();
return otkResponse;
};
});
fakeEmitter.emit('toDeviceEvent', {
getId: jest.fn().mockReturnValue("$wedged"),
getType: jest.fn().mockReturnValue('m.room.message'),
getContent: jest.fn().mockReturnValue({
msgtype: 'm.bad.encrypted',
}),
getWireContent: jest.fn().mockReturnValue({
algorithm: 'm.olm.v1.curve25519-aes-sha2',
sender_key: 'this is a key',
}),
getSender: jest.fn().mockReturnValue('@bob:home.server'),
});
await prom;
});
});
describe('Key requests', function() {
let aliceClient;
let bobClient;
beforeEach(async function() {
aliceClient = (new TestClient(
"@alice:example.com", "alicedevice",
)).client;
bobClient = (new TestClient(
"@bob:example.com", "bobdevice",
)).client;
await aliceClient.initCrypto();
await bobClient.initCrypto();
});
afterEach(async function() {
aliceClient.stopClient();
bobClient.stopClient();
});
it(
"does not cancel keyshare requests if some messages are not decrypted",
async function() {
function awaitEvent(emitter, event) {
return new Promise((resolve, reject) => {
emitter.once(event, (result) => {
resolve(result);
});
});
}
async function keyshareEventForEvent(event, index) {
const eventContent = event.getWireContent();
const key = await aliceClient._crypto._olmDevice
.getInboundGroupSessionKey(
roomId, eventContent.sender_key, eventContent.session_id,
index,
);
const ksEvent = new MatrixEvent({
type: "m.forwarded_room_key",
sender: "@alice:example.com",
content: {
algorithm: olmlib.MEGOLM_ALGORITHM,
room_id: roomId,
sender_key: eventContent.sender_key,
sender_claimed_ed25519_key: key.sender_claimed_ed25519_key,
session_id: eventContent.session_id,
session_key: key.key,
chain_index: key.chain_index,
forwarding_curve25519_key_chain:
key.forwarding_curve_key_chain,
},
});
// make onRoomKeyEvent think this was an encrypted event
ksEvent._senderCurve25519Key = "akey";
return ksEvent;
}
const encryptionCfg = {
"algorithm": "m.megolm.v1.aes-sha2",
};
const roomId = "!someroom";
const aliceRoom = new Room(roomId, aliceClient, "@alice:example.com", {});
const bobRoom = new Room(roomId, bobClient, "@bob:example.com", {});
aliceClient.store.storeRoom(aliceRoom);
bobClient.store.storeRoom(bobRoom);
await aliceClient.setRoomEncryption(roomId, encryptionCfg);
await bobClient.setRoomEncryption(roomId, encryptionCfg);
const events = [
new MatrixEvent({
type: "m.room.message",
sender: "@alice:example.com",
room_id: roomId,
event_id: "$1",
content: {
msgtype: "m.text",
body: "1",
},
}),
new MatrixEvent({
type: "m.room.message",
sender: "@alice:example.com",
room_id: roomId,
event_id: "$2",
content: {
msgtype: "m.text",
body: "2",
},
}),
];
await Promise.all(events.map(async (event) => {
// alice encrypts each event, and then bob tries to decrypt
// them without any keys, so that they'll be in pending
await aliceClient._crypto.encryptEvent(event, aliceRoom);
event._clearEvent = {};
event._senderCurve25519Key = null;
event._claimedEd25519Key = null;
try {
await bobClient._crypto.decryptEvent(event);
} catch (e) {
// we expect this to fail because we don't have the
// decryption keys yet
}
}));
const bobDecryptor = bobClient._crypto._getRoomDecryptor(
roomId, olmlib.MEGOLM_ALGORITHM,
);
let eventPromise = Promise.all(events.map((ev) => {
return awaitEvent(ev, "Event.decrypted");
}));
// keyshare the session key starting at the second message, so
// the first message can't be decrypted yet, but the second one
// can
let ksEvent = await keyshareEventForEvent(events[1], 1);
await bobDecryptor.onRoomKeyEvent(ksEvent);
await eventPromise;
expect(events[0].getContent().msgtype).toBe("m.bad.encrypted");
expect(events[1].getContent().msgtype).not.toBe("m.bad.encrypted");
const cryptoStore = bobClient._cryptoStore;
const eventContent = events[0].getWireContent();
const senderKey = eventContent.sender_key;
const sessionId = eventContent.session_id;
const roomKeyRequestBody = {
algorithm: olmlib.MEGOLM_ALGORITHM,
room_id: roomId,
sender_key: senderKey,
session_id: sessionId,
};
// the room key request should still be there, since we haven't
// decrypted everything
expect(await cryptoStore.getOutgoingRoomKeyRequest(roomKeyRequestBody))
.toBeDefined();
// keyshare the session key starting at the first message, so
// that it can now be decrypted
eventPromise = awaitEvent(events[0], "Event.decrypted");
ksEvent = await keyshareEventForEvent(events[0], 0);
await bobDecryptor.onRoomKeyEvent(ksEvent);
await eventPromise;
expect(events[0].getContent().msgtype).not.toBe("m.bad.encrypted");
await sleep(1);
// the room key request should be gone since we've now decrypted everything
expect(await cryptoStore.getOutgoingRoomKeyRequest(roomKeyRequestBody))
.toBeFalsy();
},
);
it("creates a new keyshare request if we request a keyshare", async function() {
// make sure that cancelAndResend... creates a new keyshare request
// if there wasn't an already-existing one
const event = new MatrixEvent({
sender: "@bob:example.com",
room_id: "!someroom",
content: {
algorithm: olmlib.MEGOLM_ALGORITHM,
session_id: "sessionid",
sender_key: "senderkey",
},
});
await aliceClient.cancelAndResendEventRoomKeyRequest(event);
const cryptoStore = aliceClient._cryptoStore;
const roomKeyRequestBody = {
algorithm: olmlib.MEGOLM_ALGORITHM,
room_id: "!someroom",
session_id: "sessionid",
sender_key: "senderkey",
};
expect(await cryptoStore.getOutgoingRoomKeyRequest(roomKeyRequestBody))
.toBeDefined();
});
it("uses a new txnid for re-requesting keys", async function() {
jest.useFakeTimers();
const event = new MatrixEvent({
sender: "@bob:example.com",
room_id: "!someroom",
content: {
algorithm: olmlib.MEGOLM_ALGORITHM,
session_id: "sessionid",
sender_key: "senderkey",
},
});
// replace Alice's sendToDevice function with a mock
aliceClient.sendToDevice = jest.fn().mockResolvedValue(undefined);
aliceClient.startClient();
// make a room key request, and record the transaction ID for the
// sendToDevice call
await aliceClient.cancelAndResendEventRoomKeyRequest(event);
// key requests get queued until the sync has finished, but we don't
// let the client set up enough for that to happen, so gut-wrench a bit
// to force it to send now.
aliceClient._crypto._outgoingRoomKeyRequestManager.sendQueuedRequests();
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
await aliceClient.cancelAndResendEventRoomKeyRequest(event);
jest.runAllTimers();
await Promise.resolve();
// cancelAndResend will call sendToDevice twice:
// the first call to sendToDevice will be the cancellation
// the second call to sendToDevice will be the key request
expect(aliceClient.sendToDevice).toBeCalledTimes(3);
expect(aliceClient.sendToDevice.mock.calls[2][2]).not.toBe(txnId);
});
});
});
+250
View File
@@ -0,0 +1,250 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
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 '../../olm-loader';
import {
CrossSigningInfo,
createCryptoStoreCacheCallbacks,
} from '../../../src/crypto/CrossSigning';
import {
IndexedDBCryptoStore,
} from '../../../src/crypto/store/indexeddb-crypto-store';
import {MemoryCryptoStore} from '../../../src/crypto/store/memory-crypto-store';
import 'fake-indexeddb/auto';
import 'jest-localstorage-mock';
import {OlmDevice} from "../../../src/crypto/OlmDevice";
const userId = "@alice:example.com";
// Private key for tests only
const testKey = new Uint8Array([
0xda, 0x5a, 0x27, 0x60, 0xe3, 0x3a, 0xc5, 0x82,
0x9d, 0x12, 0xc3, 0xbe, 0xe8, 0xaa, 0xc2, 0xef,
0xae, 0xb1, 0x05, 0xc1, 0xe7, 0x62, 0x78, 0xa6,
0xd7, 0x1f, 0xf8, 0x2c, 0x51, 0x85, 0xf0, 0x1d,
]);
const types = [
{ type: "master", shouldCache: false },
{ type: "self_signing", shouldCache: true },
{ type: "user_signing", shouldCache: true },
{ type: "invalid", shouldCache: false },
];
const badKey = Uint8Array.from(testKey);
badKey[0] ^= 1;
const masterKeyPub = "nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk";
describe("CrossSigningInfo.getCrossSigningKey", function() {
if (!global.Olm) {
console.warn('Not running megolm backup unit tests: libolm not present');
return;
}
beforeAll(function() {
return global.Olm.init();
});
it("should throw if no callback is provided", async () => {
const info = new CrossSigningInfo(userId);
await expect(info.getCrossSigningKey("master")).rejects.toThrow();
});
it.each(types)("should throw if the callback returns falsey",
async ({type, shouldCache}) => {
const info = new CrossSigningInfo(userId, {
getCrossSigningKey: () => false,
});
await expect(info.getCrossSigningKey(type)).rejects.toThrow("falsey");
});
it("should throw if the expected key doesn't come back", async () => {
const info = new CrossSigningInfo(userId, {
getCrossSigningKey: () => masterKeyPub,
});
await expect(info.getCrossSigningKey("master", "")).rejects.toThrow();
});
it("should return a key from its callback", async () => {
const info = new CrossSigningInfo(userId, {
getCrossSigningKey: () => testKey,
});
const [pubKey, ab] = await info.getCrossSigningKey("master", masterKeyPub);
expect(pubKey).toEqual(masterKeyPub);
expect(ab).toEqual({a: 106712, b: 106712});
});
it.each(types)("should request a key from the cache callback (if set)" +
" and does not call app if one is found" +
" %o",
async ({ type, shouldCache }) => {
const getCrossSigningKey = jest.fn().mockImplementation(() => {
if (shouldCache) {
return Promise.reject(new Error("Regular callback called"));
} else {
return Promise.resolve(testKey);
}
});
const getCrossSigningKeyCache = jest.fn().mockResolvedValue(testKey);
const info = new CrossSigningInfo(
userId,
{ getCrossSigningKey },
{ getCrossSigningKeyCache },
);
const [pubKey] = await info.getCrossSigningKey(type, masterKeyPub);
expect(pubKey).toEqual(masterKeyPub);
expect(getCrossSigningKeyCache.mock.calls.length).toBe(shouldCache ? 1 : 0);
if (shouldCache) {
expect(getCrossSigningKeyCache.mock.calls[0][0]).toBe(type);
}
});
it.each(types)("should store a key with the cache callback (if set)",
async ({ type, shouldCache }) => {
const getCrossSigningKey = jest.fn().mockResolvedValue(testKey);
const storeCrossSigningKeyCache = jest.fn().mockResolvedValue(undefined);
const info = new CrossSigningInfo(
userId,
{ getCrossSigningKey },
{ storeCrossSigningKeyCache },
);
const [pubKey] = await info.getCrossSigningKey(type, masterKeyPub);
expect(pubKey).toEqual(masterKeyPub);
expect(storeCrossSigningKeyCache.mock.calls.length).toEqual(shouldCache ? 1 : 0);
if (shouldCache) {
expect(storeCrossSigningKeyCache.mock.calls[0][0]).toBe(type);
expect(storeCrossSigningKeyCache.mock.calls[0][1]).toBe(testKey);
}
});
it.each(types)("does not store a bad key to the cache",
async ({ type, shouldCache }) => {
const getCrossSigningKey = jest.fn().mockResolvedValue(badKey);
const storeCrossSigningKeyCache = jest.fn().mockResolvedValue(undefined);
const info = new CrossSigningInfo(
userId,
{ getCrossSigningKey },
{ storeCrossSigningKeyCache },
);
await expect(info.getCrossSigningKey(type, masterKeyPub)).rejects.toThrow();
expect(storeCrossSigningKeyCache.mock.calls.length).toEqual(0);
});
it.each(types)("does not store a value to the cache if it came from the cache",
async ({ type, shouldCache }) => {
const getCrossSigningKey = jest.fn().mockImplementation(() => {
if (shouldCache) {
return Promise.reject(new Error("Regular callback called"));
} else {
return Promise.resolve(testKey);
}
});
const getCrossSigningKeyCache = jest.fn().mockResolvedValue(testKey);
const storeCrossSigningKeyCache = jest.fn().mockRejectedValue(
new Error("Tried to store a value from cache"),
);
const info = new CrossSigningInfo(
userId,
{ getCrossSigningKey },
{ getCrossSigningKeyCache, storeCrossSigningKeyCache },
);
expect(storeCrossSigningKeyCache.mock.calls.length).toBe(0);
const [pubKey] = await info.getCrossSigningKey(type, masterKeyPub);
expect(pubKey).toEqual(masterKeyPub);
});
it.each(types)("requests a key from the cache callback (if set) and then calls app" +
" if one is not found", async ({ type, shouldCache }) => {
const getCrossSigningKey = jest.fn().mockResolvedValue(testKey);
const getCrossSigningKeyCache = jest.fn().mockResolvedValue(undefined);
const storeCrossSigningKeyCache = jest.fn();
const info = new CrossSigningInfo(
userId,
{ getCrossSigningKey },
{ getCrossSigningKeyCache, storeCrossSigningKeyCache },
);
const [pubKey] = await info.getCrossSigningKey(type, masterKeyPub);
expect(pubKey).toEqual(masterKeyPub);
expect(getCrossSigningKey.mock.calls.length).toBe(1);
expect(getCrossSigningKeyCache.mock.calls.length).toBe(shouldCache ? 1 : 0);
/* Also expect that the cache gets updated */
expect(storeCrossSigningKeyCache.mock.calls.length).toBe(shouldCache ? 1 : 0);
});
it.each(types)("requests a key from the cache callback (if set) and then" +
" calls app if that key doesn't match", async ({ type, shouldCache }) => {
const getCrossSigningKey = jest.fn().mockResolvedValue(testKey);
const getCrossSigningKeyCache = jest.fn().mockResolvedValue(badKey);
const storeCrossSigningKeyCache = jest.fn();
const info = new CrossSigningInfo(
userId,
{ getCrossSigningKey },
{ getCrossSigningKeyCache, storeCrossSigningKeyCache },
);
const [pubKey] = await info.getCrossSigningKey(type, masterKeyPub);
expect(pubKey).toEqual(masterKeyPub);
expect(getCrossSigningKey.mock.calls.length).toBe(1);
expect(getCrossSigningKeyCache.mock.calls.length).toBe(shouldCache ? 1 : 0);
/* Also expect that the cache gets updated */
expect(storeCrossSigningKeyCache.mock.calls.length).toBe(shouldCache ? 1 : 0);
});
});
/*
* Note that MemoryStore is weird. It's only used for testing - as far as I can tell,
* it's not possible to get one in normal execution unless you hack as we do here.
*/
describe.each([
["IndexedDBCryptoStore",
() => new IndexedDBCryptoStore(global.indexedDB, "tests")],
["LocalStorageCryptoStore",
() => new IndexedDBCryptoStore(undefined, "tests")],
["MemoryCryptoStore", () => {
const store = new IndexedDBCryptoStore(undefined, "tests");
store._backend = new MemoryCryptoStore();
store._backendPromise = Promise.resolve(store._backend);
return store;
}],
])("CrossSigning > createCryptoStoreCacheCallbacks [%s]", function(name, dbFactory) {
let store;
beforeAll(() => {
store = dbFactory();
});
beforeEach(async () => {
await store.deleteAllData();
});
it("should cache data to the store and retrieve it", async () => {
await store.startup();
const olmDevice = new OlmDevice(store);
const { getCrossSigningKeyCache, storeCrossSigningKeyCache } =
createCryptoStoreCacheCallbacks(store, olmDevice);
await storeCrossSigningKeyCache("self_signing", testKey);
// If we've not saved anything, don't expect anything
// Definitely don't accidentally return the wrong key for the type
const nokey = await getCrossSigningKeyCache("self", "");
expect(nokey).toBeNull();
const key = await getCrossSigningKeyCache("self_signing", "");
expect(new Uint8Array(key)).toEqual(testKey);
});
});
+57 -34
View File
@@ -1,11 +1,25 @@
import DeviceList from '../../../lib/crypto/DeviceList';
import MockStorageApi from '../../MockStorageApi';
import WebStorageSessionStore from '../../../lib/store/session/webstorage';
import testUtils from '../../test-utils';
import utils from '../../../lib/utils';
/*
Copyright 2017 Vector Creations Ltd
Copyright 2018, 2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
import expect from 'expect';
import Promise from 'bluebird';
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 "../../../src/logger";
import * as utils from "../../../src/utils";
import {MemoryCryptoStore} from "../../../src/crypto/store/memory-crypto-store";
import {DeviceList} from "../../../src/crypto/DeviceList";
const signedDeviceList = {
"failures": {},
@@ -39,14 +53,20 @@ const signedDeviceList = {
describe('DeviceList', function() {
let downloadSpy;
let sessionStore;
let cryptoStore;
let deviceLists = [];
beforeEach(function() {
testUtils.beforeEach(this); // eslint-disable-line no-invalid-this
deviceLists = [];
downloadSpy = expect.createSpy();
const mockStorage = new MockStorageApi();
sessionStore = new WebStorageSessionStore(mockStorage);
downloadSpy = jest.fn();
cryptoStore = new MemoryCryptoStore();
});
afterEach(function() {
for (const dl of deviceLists) {
dl.stop();
}
});
function createTestDeviceList() {
@@ -56,7 +76,9 @@ describe('DeviceList', function() {
const mockOlm = {
verifySignature: function(key, message, signature) {},
};
return new DeviceList(baseApis, sessionStore, mockOlm);
const dl = new DeviceList(baseApis, cryptoStore, mockOlm);
deviceLists.push(dl);
return dl;
}
it("should successfully download and store device keys", function() {
@@ -64,15 +86,15 @@ describe('DeviceList', function() {
dl.startTrackingDeviceList('@test1:sw1v.org');
const queryDefer1 = Promise.defer();
downloadSpy.andReturn(queryDefer1.promise);
const queryDefer1 = utils.defer();
downloadSpy.mockReturnValue(queryDefer1.promise);
const prom1 = dl.refreshOutdatedDeviceLists();
expect(downloadSpy).toHaveBeenCalledWith(['@test1:sw1v.org'], {});
queryDefer1.resolve(utils.deepCopy(signedDeviceList));
return prom1.then(() => {
const storedKeys = sessionStore.getEndToEndDevicesForUser('@test1:sw1v.org');
const storedKeys = dl.getRawStoredDevicesForUser('@test1:sw1v.org');
expect(Object.keys(storedKeys)).toEqual(['HGKAWHRVJQ']);
});
});
@@ -83,35 +105,36 @@ describe('DeviceList', function() {
dl.startTrackingDeviceList('@test1:sw1v.org');
const queryDefer1 = Promise.defer();
downloadSpy.andReturn(queryDefer1.promise);
const queryDefer1 = utils.defer();
downloadSpy.mockReturnValue(queryDefer1.promise);
const prom1 = dl.refreshOutdatedDeviceLists();
expect(downloadSpy).toHaveBeenCalledWith(['@test1:sw1v.org'], {});
downloadSpy.reset();
downloadSpy.mockReset();
// outdated notif arrives while the request is in flight.
const queryDefer2 = Promise.defer();
downloadSpy.andReturn(queryDefer2.promise);
const queryDefer2 = utils.defer();
downloadSpy.mockReturnValue(queryDefer2.promise);
dl.invalidateUserDeviceList('@test1:sw1v.org');
dl.refreshOutdatedDeviceLists();
// the first request completes
queryDefer1.resolve({
device_keys: {
'@test1:sw1v.org': {},
},
});
return prom1.then(() => {
dl.saveIfDirty().then(() => {
// the first request completes
queryDefer1.resolve({
device_keys: {
'@test1:sw1v.org': {},
},
});
return prom1;
}).then(() => {
// uh-oh; user restarts before second request completes. The new instance
// should know we never got a complete device list.
console.log("Creating new devicelist to simulate app reload");
downloadSpy.reset();
logger.log("Creating new devicelist to simulate app reload");
downloadSpy.mockReset();
const dl2 = createTestDeviceList();
const queryDefer3 = Promise.defer();
downloadSpy.andReturn(queryDefer3.promise);
const queryDefer3 = utils.defer();
downloadSpy.mockReturnValue(queryDefer3.promise);
const prom3 = dl2.refreshOutdatedDeviceLists();
expect(downloadSpy).toHaveBeenCalledWith(['@test1:sw1v.org'], {});
@@ -121,7 +144,7 @@ describe('DeviceList', function() {
// allow promise chain to complete
return prom3;
}).then(() => {
const storedKeys = sessionStore.getEndToEndDevicesForUser('@test1:sw1v.org');
const storedKeys = dl.getRawStoredDevicesForUser('@test1:sw1v.org');
expect(Object.keys(storedKeys)).toEqual(['HGKAWHRVJQ']);
});
});
+480 -54
View File
@@ -1,52 +1,46 @@
try {
global.Olm = require('olm');
} catch (e) {
console.warn("unable to run megolm tests: libolm not available");
}
import '../../../olm-loader';
import * as algorithms from "../../../../src/crypto/algorithms";
import {MemoryCryptoStore} from "../../../../src/crypto/store/memory-crypto-store";
import {MockStorageApi} from "../../../MockStorageApi";
import * as testUtils from "../../../test-utils";
import {OlmDevice} from "../../../../src/crypto/OlmDevice";
import {Crypto} from "../../../../src/crypto";
import {logger} from "../../../../src/logger";
import {MatrixEvent} from "../../../../src/models/event";
import {TestClient} from "../../../TestClient";
import {Room} from "../../../../src/models/room";
import * as olmlib from "../../../../src/crypto/olmlib";
import expect from 'expect';
import Promise from 'bluebird';
import sdk from '../../../..';
import algorithms from '../../../../lib/crypto/algorithms';
import WebStorageSessionStore from '../../../../lib/store/session/webstorage';
import MockStorageApi from '../../../MockStorageApi';
import testUtils from '../../../test-utils';
// Crypto and OlmDevice won't import unless we have global.Olm
let OlmDevice;
let Crypto;
if (global.Olm) {
OlmDevice = require('../../../../lib/crypto/OlmDevice');
Crypto = require('../../../../lib/crypto');
}
const MatrixEvent = sdk.MatrixEvent;
const MegolmDecryption = algorithms.DECRYPTION_CLASSES['m.megolm.v1.aes-sha2'];
const MegolmEncryption = algorithms.ENCRYPTION_CLASSES['m.megolm.v1.aes-sha2'];
const ROOM_ID = '!ROOM:ID';
const Olm = global.Olm;
describe("MegolmDecryption", function() {
if (!global.Olm) {
console.warn('Not running megolm unit tests: libolm not present');
logger.warn('Not running megolm unit tests: libolm not present');
return;
}
beforeAll(function() {
return Olm.init();
});
let megolmDecryption;
let mockOlmLib;
let mockCrypto;
let mockBaseApis;
beforeEach(function() {
testUtils.beforeEach(this); // eslint-disable-line no-invalid-this
beforeEach(async function() {
mockCrypto = testUtils.mock(Crypto, 'Crypto');
mockBaseApis = {};
const mockStorage = new MockStorageApi();
const sessionStore = new WebStorageSessionStore(mockStorage);
const cryptoStore = new MemoryCryptoStore(mockStorage);
const olmDevice = new OlmDevice(sessionStore);
const olmDevice = new OlmDevice(cryptoStore);
megolmDecryption = new MegolmDecryption({
userId: '@user:id',
@@ -59,15 +53,15 @@ describe("MegolmDecryption", function() {
// we stub out the olm encryption bits
mockOlmLib = {};
mockOlmLib.ensureOlmSessionsForDevices = expect.createSpy();
mockOlmLib.ensureOlmSessionsForDevices = jest.fn();
mockOlmLib.encryptMessageForDevice =
expect.createSpy().andReturn(Promise.resolve());
jest.fn().mockResolvedValue(undefined);
megolmDecryption.olmlib = mockOlmLib;
});
describe('receives some keys:', function() {
let groupSession;
beforeEach(function() {
beforeEach(async function() {
groupSession = new global.Olm.OutboundGroupSession();
groupSession.create();
@@ -96,7 +90,7 @@ describe("MegolmDecryption", function() {
},
};
return event.attemptDecryption(mockCrypto).then(() => {
await event.attemptDecryption(mockCrypto).then(() => {
megolmDecryption.onRoomKeyEvent(event);
});
});
@@ -139,22 +133,22 @@ describe("MegolmDecryption", function() {
// set up some pre-conditions for the share call
const deviceInfo = {};
mockCrypto.getStoredDevice.andReturn(deviceInfo);
mockCrypto.getStoredDevice.mockReturnValue(deviceInfo);
mockOlmLib.ensureOlmSessionsForDevices.andReturn(
Promise.resolve({'@alice:foo': {'alidevice': {
mockOlmLib.ensureOlmSessionsForDevices.mockResolvedValue({
'@alice:foo': {'alidevice': {
sessionId: 'alisession',
}}}),
);
}},
});
const awaitEncryptForDevice = new Promise((res, rej) => {
mockOlmLib.encryptMessageForDevice.andCall(() => {
mockOlmLib.encryptMessageForDevice.mockImplementation(() => {
res();
return Promise.resolve();
});
});
mockBaseApis.sendToDevice = expect.createSpy();
mockBaseApis.sendToDevice = jest.fn();
// do the share
megolmDecryption.shareKeysWithDevice(keyRequest);
@@ -164,21 +158,20 @@ describe("MegolmDecryption", function() {
}).then(() => {
// check that it called encryptMessageForDevice with
// appropriate args.
expect(mockOlmLib.encryptMessageForDevice.calls.length)
.toEqual(1);
expect(mockOlmLib.encryptMessageForDevice).toBeCalledTimes(1);
const call = mockOlmLib.encryptMessageForDevice.calls[0];
const payload = call.arguments[6];
const call = mockOlmLib.encryptMessageForDevice.mock.calls[0];
const payload = call[6];
expect(payload.type).toEqual("m.forwarded_room_key");
expect(payload.content).toInclude({
expect(payload.content).toMatchObject({
sender_key: "SENDER_CURVE25519",
sender_claimed_ed25519_key: "SENDER_ED25519",
session_id: groupSession.session_id(),
chain_index: 0,
forwarding_curve25519_key_chain: [],
});
expect(payload.content.session_key).toExist();
expect(payload.content.session_key).toBeDefined();
});
});
@@ -205,13 +198,12 @@ describe("MegolmDecryption", function() {
origin_server_ts: 1507753886000,
});
const successHandler = expect.createSpy();
const failureHandler = expect.createSpy()
.andCall((err) => {
expect(err.toString()).toMatch(
/Duplicate message index, possible replay attack/,
);
});
const successHandler = jest.fn();
const failureHandler = jest.fn((err) => {
expect(err.toString()).toMatch(
/Duplicate message index, possible replay attack/,
);
});
return megolmDecryption.decryptEvent(event1).then((res) => {
const event2 = new MatrixEvent({
@@ -232,7 +224,7 @@ describe("MegolmDecryption", function() {
successHandler,
failureHandler,
).then(() => {
expect(successHandler).toNotHaveBeenCalled();
expect(successHandler).not.toHaveBeenCalled();
expect(failureHandler).toHaveBeenCalled();
});
});
@@ -264,5 +256,439 @@ describe("MegolmDecryption", function() {
// test is successful if no exception is thrown
});
});
it("re-uses sessions for sequential messages", async function() {
const mockStorage = new MockStorageApi();
const cryptoStore = new MemoryCryptoStore(mockStorage);
const olmDevice = new OlmDevice(cryptoStore);
olmDevice.verifySignature = jest.fn();
await olmDevice.init();
mockBaseApis.claimOneTimeKeys = jest.fn().mockReturnValue(Promise.resolve({
one_time_keys: {
'@alice:home.server': {
aliceDevice: {
'signed_curve25519:flooble': {
key: 'YmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmI',
signatures: {
'@alice:home.server': {
'ed25519:aliceDevice': 'totally valid',
},
},
},
},
},
},
}));
mockBaseApis.sendToDevice = jest.fn().mockResolvedValue(undefined);
mockCrypto.downloadKeys.mockReturnValue(Promise.resolve({
'@alice:home.server': {
aliceDevice: {
deviceId: 'aliceDevice',
isBlocked: jest.fn().mockReturnValue(false),
isUnverified: jest.fn().mockReturnValue(false),
getIdentityKey: jest.fn().mockReturnValue(
'YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWE',
),
getFingerprint: jest.fn().mockReturnValue(''),
},
},
}));
mockCrypto.checkDeviceTrust.mockReturnValue({
isVerified: () => false,
});
const megolmEncryption = new MegolmEncryption({
userId: '@user:id',
crypto: mockCrypto,
olmDevice: olmDevice,
baseApis: mockBaseApis,
roomId: ROOM_ID,
config: {
rotation_period_ms: 9999999999999,
},
});
const mockRoom = {
getEncryptionTargetMembers: jest.fn().mockReturnValue(
[{userId: "@alice:home.server"}],
),
getBlacklistUnverifiedDevices: jest.fn().mockReturnValue(false),
};
const ct1 = await megolmEncryption.encryptMessage(mockRoom, "a.fake.type", {
body: "Some text",
});
expect(mockRoom.getEncryptionTargetMembers).toHaveBeenCalled();
// this should have claimed a key for alice as it's starting a new session
expect(mockBaseApis.claimOneTimeKeys).toHaveBeenCalledWith(
[['@alice:home.server', 'aliceDevice']], 'signed_curve25519', 2000,
);
expect(mockCrypto.downloadKeys).toHaveBeenCalledWith(
['@alice:home.server'], false,
);
expect(mockBaseApis.sendToDevice).toHaveBeenCalled();
expect(mockBaseApis.claimOneTimeKeys).toHaveBeenCalledWith(
[['@alice:home.server', 'aliceDevice']], 'signed_curve25519', 2000,
);
mockBaseApis.claimOneTimeKeys.mockReset();
const ct2 = await megolmEncryption.encryptMessage(mockRoom, "a.fake.type", {
body: "Some more text",
});
// this should *not* have claimed a key as it should be using the same session
expect(mockBaseApis.claimOneTimeKeys).not.toHaveBeenCalled();
// likewise they should show the same session ID
expect(ct2.session_id).toEqual(ct1.session_id);
});
});
it("notifies devices that have been blocked", async function() {
const aliceClient = (new TestClient(
"@alice:example.com", "alicedevice",
)).client;
const bobClient1 = (new TestClient(
"@bob:example.com", "bobdevice1",
)).client;
const bobClient2 = (new TestClient(
"@bob:example.com", "bobdevice2",
)).client;
await Promise.all([
aliceClient.initCrypto(),
bobClient1.initCrypto(),
bobClient2.initCrypto(),
]);
const aliceDevice = aliceClient._crypto._olmDevice;
const bobDevice1 = bobClient1._crypto._olmDevice;
const bobDevice2 = bobClient2._crypto._olmDevice;
const encryptionCfg = {
"algorithm": "m.megolm.v1.aes-sha2",
};
const roomId = "!someroom";
const room = new Room(roomId, aliceClient, "@alice:example.com", {});
room.getEncryptionTargetMembers = async function() {
return [{userId: "@bob:example.com"}];
};
room.setBlacklistUnverifiedDevices(true);
aliceClient.store.storeRoom(room);
await aliceClient.setRoomEncryption(roomId, encryptionCfg);
const BOB_DEVICES = {
bobdevice1: {
user_id: "@bob:example.com",
device_id: "bobdevice1",
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
keys: {
"ed25519:Dynabook": bobDevice1.deviceEd25519Key,
"curve25519:Dynabook": bobDevice1.deviceCurve25519Key,
},
verified: 0,
},
bobdevice2: {
user_id: "@bob:example.com",
device_id: "bobdevice2",
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
keys: {
"ed25519:Dynabook": bobDevice2.deviceEd25519Key,
"curve25519:Dynabook": bobDevice2.deviceCurve25519Key,
},
verified: -1,
},
};
aliceClient._crypto._deviceList.storeDevicesForUser(
"@bob:example.com", BOB_DEVICES,
);
aliceClient._crypto._deviceList.downloadKeys = async function(userIds) {
return this._getDevicesFromStore(userIds);
};
let run = false;
aliceClient.sendToDevice = async (msgtype, contentMap) => {
run = true;
expect(msgtype).toBe("org.matrix.room_key.withheld");
delete contentMap["@bob:example.com"].bobdevice1.session_id;
delete contentMap["@bob:example.com"].bobdevice2.session_id;
expect(contentMap).toStrictEqual({
'@bob:example.com': {
bobdevice1: {
algorithm: "m.megolm.v1.aes-sha2",
room_id: roomId,
code: 'm.unverified',
reason:
'The sender has disabled encrypting to unverified devices.',
sender_key: aliceDevice.deviceCurve25519Key,
},
bobdevice2: {
algorithm: "m.megolm.v1.aes-sha2",
room_id: roomId,
code: 'm.blacklisted',
reason: 'The sender has blocked you.',
sender_key: aliceDevice.deviceCurve25519Key,
},
},
});
};
const event = new MatrixEvent({
type: "m.room.message",
sender: "@alice:example.com",
room_id: roomId,
event_id: "$event",
content: {
msgtype: "m.text",
body: "secret",
},
});
await aliceClient._crypto.encryptEvent(event, room);
expect(run).toBe(true);
aliceClient.stopClient();
bobClient1.stopClient();
bobClient2.stopClient();
});
it("notifies devices when unable to create olm session", async function() {
const aliceClient = (new TestClient(
"@alice:example.com", "alicedevice",
)).client;
const bobClient = (new TestClient(
"@bob:example.com", "bobdevice",
)).client;
await Promise.all([
aliceClient.initCrypto(),
bobClient.initCrypto(),
]);
const aliceDevice = aliceClient._crypto._olmDevice;
const bobDevice = bobClient._crypto._olmDevice;
const encryptionCfg = {
"algorithm": "m.megolm.v1.aes-sha2",
};
const roomId = "!someroom";
const aliceRoom = new Room(roomId, aliceClient, "@alice:example.com", {});
const bobRoom = new Room(roomId, bobClient, "@bob:example.com", {});
aliceClient.store.storeRoom(aliceRoom);
bobClient.store.storeRoom(bobRoom);
await aliceClient.setRoomEncryption(roomId, encryptionCfg);
await bobClient.setRoomEncryption(roomId, encryptionCfg);
aliceRoom.getEncryptionTargetMembers = async () => {
return [
{
userId: "@alice:example.com",
membership: "join",
},
{
userId: "@bob:example.com",
membership: "join",
},
];
};
const BOB_DEVICES = {
bobdevice: {
user_id: "@bob:example.com",
device_id: "bobdevice",
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
keys: {
"ed25519:bobdevice": bobDevice.deviceEd25519Key,
"curve25519:bobdevice": bobDevice.deviceCurve25519Key,
},
known: true,
verified: 1,
},
};
aliceClient._crypto._deviceList.storeDevicesForUser(
"@bob:example.com", BOB_DEVICES,
);
aliceClient._crypto._deviceList.downloadKeys = async function(userIds) {
return this._getDevicesFromStore(userIds);
};
aliceClient.claimOneTimeKeys = async () => {
// Bob has no one-time keys
return {
one_time_keys: {},
};
};
const sendPromise = new Promise((resolve, reject) => {
aliceClient.sendToDevice = async (msgtype, contentMap) => {
expect(msgtype).toBe("org.matrix.room_key.withheld");
expect(contentMap).toStrictEqual({
'@bob:example.com': {
bobdevice: {
algorithm: "m.megolm.v1.aes-sha2",
code: 'm.no_olm',
reason: 'Unable to establish a secure channel.',
sender_key: aliceDevice.deviceCurve25519Key,
},
},
});
resolve();
};
});
const event = new MatrixEvent({
type: "m.room.message",
sender: "@alice:example.com",
room_id: roomId,
event_id: "$event",
content: {},
});
await aliceClient._crypto.encryptEvent(event, aliceRoom);
await sendPromise;
});
it("throws an error describing why it doesn't have a key", async function() {
const aliceClient = (new TestClient(
"@alice:example.com", "alicedevice",
)).client;
const bobClient = (new TestClient(
"@bob:example.com", "bobdevice",
)).client;
await Promise.all([
aliceClient.initCrypto(),
bobClient.initCrypto(),
]);
const bobDevice = bobClient._crypto._olmDevice;
const roomId = "!someroom";
aliceClient._crypto._onToDeviceEvent(new MatrixEvent({
type: "org.matrix.room_key.withheld",
sender: "@bob:example.com",
content: {
algorithm: "m.megolm.v1.aes-sha2",
room_id: roomId,
session_id: "session_id",
sender_key: bobDevice.deviceCurve25519Key,
code: "m.blacklisted",
reason: "You have been blocked",
},
}));
await expect(aliceClient._crypto.decryptEvent(new MatrixEvent({
type: "m.room.encrypted",
sender: "@bob:example.com",
event_id: "$event",
room_id: roomId,
content: {
algorithm: "m.megolm.v1.aes-sha2",
ciphertext: "blablabla",
device_id: "bobdevice",
sender_key: bobDevice.deviceCurve25519Key,
session_id: "session_id",
},
}))).rejects.toThrow("The sender has blocked you.");
});
it("throws an error describing the lack of an olm session", async function() {
const aliceClient = (new TestClient(
"@alice:example.com", "alicedevice",
)).client;
const bobClient = (new TestClient(
"@bob:example.com", "bobdevice",
)).client;
await Promise.all([
aliceClient.initCrypto(),
bobClient.initCrypto(),
]);
aliceClient._crypto.downloadKeys = async () => {};
const bobDevice = bobClient._crypto._olmDevice;
const roomId = "!someroom";
const now = Date.now();
aliceClient._crypto._onToDeviceEvent(new MatrixEvent({
type: "org.matrix.room_key.withheld",
sender: "@bob:example.com",
content: {
algorithm: "m.megolm.v1.aes-sha2",
room_id: roomId,
session_id: "session_id",
sender_key: bobDevice.deviceCurve25519Key,
code: "m.no_olm",
reason: "Unable to establish a secure channel.",
},
}));
await new Promise((resolve) => {
setTimeout(resolve, 100);
});
await expect(aliceClient._crypto.decryptEvent(new MatrixEvent({
type: "m.room.encrypted",
sender: "@bob:example.com",
event_id: "$event",
room_id: roomId,
content: {
algorithm: "m.megolm.v1.aes-sha2",
ciphertext: "blablabla",
device_id: "bobdevice",
sender_key: bobDevice.deviceCurve25519Key,
session_id: "session_id",
},
origin_server_ts: now,
}))).rejects.toThrow("The sender was unable to establish a secure channel.");
});
it("throws an error to indicate a wedged olm session", async function() {
const aliceClient = (new TestClient(
"@alice:example.com", "alicedevice",
)).client;
const bobClient = (new TestClient(
"@bob:example.com", "bobdevice",
)).client;
await Promise.all([
aliceClient.initCrypto(),
bobClient.initCrypto(),
]);
const bobDevice = bobClient._crypto._olmDevice;
aliceClient._crypto.downloadKeys = async () => {};
const roomId = "!someroom";
const now = Date.now();
// pretend we got an event that we can't decrypt
aliceClient._crypto._onToDeviceEvent(new MatrixEvent({
type: "m.room.encrypted",
sender: "@bob:example.com",
content: {
msgtype: "m.bad.encrypted",
algorithm: "m.megolm.v1.aes-sha2",
session_id: "session_id",
sender_key: bobDevice.deviceCurve25519Key,
},
}));
await new Promise((resolve) => {
setTimeout(resolve, 100);
});
await expect(aliceClient._crypto.decryptEvent(new MatrixEvent({
type: "m.room.encrypted",
sender: "@bob:example.com",
event_id: "$event",
room_id: roomId,
content: {
algorithm: "m.megolm.v1.aes-sha2",
ciphertext: "blablabla",
device_id: "bobdevice",
sender_key: bobDevice.deviceCurve25519Key,
session_id: "session_id",
},
origin_server_ts: now,
}))).rejects.toThrow("The secure channel with the sender was corrupted.");
});
});
+194
View File
@@ -0,0 +1,194 @@
/*
Copyright 2018,2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
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 '../../../olm-loader';
import {MemoryCryptoStore} from "../../../../src/crypto/store/memory-crypto-store";
import {MockStorageApi} from "../../../MockStorageApi";
import {logger} from "../../../../src/logger";
import {OlmDevice} from "../../../../src/crypto/OlmDevice";
import * as olmlib from "../../../../src/crypto/olmlib";
import {DeviceInfo} from "../../../../src/crypto/deviceinfo";
function makeOlmDevice() {
const mockStorage = new MockStorageApi();
const cryptoStore = new MemoryCryptoStore(mockStorage);
const olmDevice = new OlmDevice(cryptoStore);
return olmDevice;
}
async function setupSession(initiator, opponent) {
await opponent.generateOneTimeKeys(1);
const keys = await opponent.getOneTimeKeys();
const firstKey = Object.values(keys['curve25519'])[0];
const sid = await initiator.createOutboundSession(
opponent.deviceCurve25519Key, firstKey,
);
return sid;
}
describe("OlmDevice", function() {
if (!global.Olm) {
logger.warn('Not running megolm unit tests: libolm not present');
return;
}
beforeAll(function() {
return global.Olm.init();
});
let aliceOlmDevice;
let bobOlmDevice;
beforeEach(async function() {
aliceOlmDevice = makeOlmDevice();
bobOlmDevice = makeOlmDevice();
await aliceOlmDevice.init();
await bobOlmDevice.init();
});
describe('olm', function() {
it("can decrypt messages", async function() {
const sid = await setupSession(aliceOlmDevice, bobOlmDevice);
const ciphertext = await aliceOlmDevice.encryptMessage(
bobOlmDevice.deviceCurve25519Key,
sid,
"The olm or proteus is an aquatic salamander in the family Proteidae",
);
const result = await bobOlmDevice.createInboundSession(
aliceOlmDevice.deviceCurve25519Key,
ciphertext.type,
ciphertext.body,
);
expect(result.payload).toEqual(
"The olm or proteus is an aquatic salamander in the family Proteidae",
);
});
it('exports picked account and olm sessions', async function() {
const sessionId = await setupSession(aliceOlmDevice, bobOlmDevice);
const exported = await bobOlmDevice.export();
// At this moment only Alice (the “initiator” in setupSession) has a session
expect(exported.sessions).toEqual([]);
const MESSAGE = (
"The olm or proteus is an aquatic salamander"
+ " in the family Proteidae"
);
const ciphertext = await aliceOlmDevice.encryptMessage(
bobOlmDevice.deviceCurve25519Key,
sessionId,
MESSAGE,
);
const bobRecreatedOlmDevice = makeOlmDevice();
bobRecreatedOlmDevice.init({ fromExportedDevice: exported });
const decrypted = await bobRecreatedOlmDevice.createInboundSession(
aliceOlmDevice.deviceCurve25519Key,
ciphertext.type,
ciphertext.body,
);
expect(decrypted.payload).toEqual(MESSAGE);
const exportedAgain = await bobRecreatedOlmDevice.export();
// this time we expect Bob to have a session to export
expect(exportedAgain.sessions).toHaveLength(1);
const MESSAGE_2 = (
"In contrast to most amphibians,"
+ " the olm is entirely aquatic"
);
const ciphertext2 = await aliceOlmDevice.encryptMessage(
bobOlmDevice.deviceCurve25519Key,
sessionId,
MESSAGE_2,
);
const bobRecreatedAgainOlmDevice = makeOlmDevice();
bobRecreatedAgainOlmDevice.init({ fromExportedDevice: exportedAgain });
// Note: "decrypted_2" does not have the same structure as "decrypted"
const decrypted2 = await bobRecreatedAgainOlmDevice.decryptMessage(
aliceOlmDevice.deviceCurve25519Key,
decrypted.session_id,
ciphertext2.type,
ciphertext2.body,
);
expect(decrypted2).toEqual(MESSAGE_2);
});
it("creates only one session at a time", async function() {
// if we call ensureOlmSessionsForDevices multiple times, it should
// only try to create one session at a time, even if the server is
// slow
let count = 0;
const baseApis = {
claimOneTimeKeys: () => {
// simulate a very slow server (.5 seconds to respond)
count++;
return new Promise((resolve, reject) => {
setTimeout(reject, 500);
});
},
};
const devicesByUser = {
"@bob:example.com": [
DeviceInfo.fromStorage({
keys: {
"curve25519:ABCDEFG": "akey",
},
}, "ABCDEFG"),
],
};
function alwaysSucceed(promise) {
// swallow any exception thrown by a promise, so that
// Promise.all doesn't abort
return promise.catch(() => {});
}
// start two tasks that try to ensure that there's an olm session
const promises = Promise.all([
alwaysSucceed(olmlib.ensureOlmSessionsForDevices(
aliceOlmDevice, baseApis, devicesByUser,
)),
alwaysSucceed(olmlib.ensureOlmSessionsForDevices(
aliceOlmDevice, baseApis, devicesByUser,
)),
]);
await new Promise((resolve) => {
setTimeout(resolve, 200);
});
// after .2s, both tasks should have started, but one should be
// waiting on the other before trying to create a session, so
// claimOneTimeKeys should have only been called once
expect(count).toBe(1);
await promises;
// after waiting for both tasks to complete, the first task should
// have failed, so the second task should have tried to create a
// new session and will have called claimOneTimeKeys
expect(count).toBe(2);
});
});
});
+571
View File
@@ -0,0 +1,571 @@
/*
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 '../../olm-loader';
import {logger} from "../../../src/logger";
import * as olmlib from "../../../src/crypto/olmlib";
import {MatrixClient} from "../../../src/client";
import {MatrixEvent} from "../../../src/models/event";
import * as algorithms from "../../../src/crypto/algorithms";
import {WebStorageSessionStore} from "../../../src/store/session/webstorage";
import {MemoryCryptoStore} from "../../../src/crypto/store/memory-crypto-store";
import {MockStorageApi} from "../../MockStorageApi";
import * as testUtils from "../../test-utils";
import {OlmDevice} from "../../../src/crypto/OlmDevice";
import {Crypto} from "../../../src/crypto";
const Olm = global.Olm;
const MegolmDecryption = algorithms.DECRYPTION_CLASSES['m.megolm.v1.aes-sha2'];
const ROOM_ID = '!ROOM:ID';
const SESSION_ID = 'o+21hSjP+mgEmcfdslPsQdvzWnkdt0Wyo00Kp++R8Kc';
const ENCRYPTED_EVENT = new MatrixEvent({
type: 'm.room.encrypted',
room_id: '!ROOM:ID',
content: {
algorithm: 'm.megolm.v1.aes-sha2',
sender_key: 'SENDER_CURVE25519',
session_id: SESSION_ID,
ciphertext: 'AwgAEjD+VwXZ7PoGPRS/H4kwpAsMp/g+WPvJVtPEKE8fmM9IcT/N'
+ 'CiwPb8PehecDKP0cjm1XO88k6Bw3D17aGiBHr5iBoP7oSw8CXULXAMTkBl'
+ 'mkufRQq2+d0Giy1s4/Cg5n13jSVrSb2q7VTSv1ZHAFjUCsLSfR0gxqcQs',
},
event_id: '$event1',
origin_server_ts: 1507753886000,
});
const KEY_BACKUP_DATA = {
first_message_index: 0,
forwarded_count: 0,
is_verified: false,
session_data: {
ciphertext: '2z2M7CZ+azAiTHN1oFzZ3smAFFt+LEOYY6h3QO3XXGdw'
+ '6YpNn/gpHDO6I/rgj1zNd4FoTmzcQgvKdU8kN20u5BWRHxaHTZ'
+ 'Slne5RxE6vUdREsBgZePglBNyG0AogR/PVdcrv/v18Y6rLM5O9'
+ 'SELmwbV63uV9Kuu/misMxoqbuqEdG7uujyaEKtjlQsJ5MGPQOy'
+ 'Syw7XrnesSwF6XWRMxcPGRV0xZr3s9PI350Wve3EncjRgJ9IGF'
+ 'ru1bcptMqfXgPZkOyGvrphHoFfoK7nY3xMEHUiaTRfRIjq8HNV'
+ '4o8QY1qmWGnxNBQgOlL8MZlykjg3ULmQ3DtFfQPj/YYGS3jzxv'
+ 'C+EBjaafmsg+52CTeK3Rswu72PX450BnSZ1i3If4xWAUKvjTpe'
+ 'Ug5aDLqttOv1pITolTJDw5W/SD+b5rjEKg1CFCHGEGE9wwV3Nf'
+ 'QHVCQL+dfpd7Or0poy4dqKMAi3g0o3Tg7edIF8d5rREmxaALPy'
+ 'iie8PHD8mj/5Y0GLqrac4CD6+Mop7eUTzVovprjg',
mac: '5lxYBHQU80M',
ephemeral: '/Bn0A4UMFwJaDDvh0aEk1XZj3k1IfgCxgFY9P9a0b14',
},
};
const BACKUP_INFO = {
algorithm: "m.megolm_backup.v1",
version: 1,
auth_data: {
public_key: "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo",
},
};
const keys = {};
function getCrossSigningKey(type) {
return keys[type];
}
function saveCrossSigningKeys(k) {
Object.assign(keys, k);
}
function makeTestClient(sessionStore, cryptoStore) {
const scheduler = [
"getQueueForEvent", "queueEvent", "removeEventFromQueue",
"setProcessFunction",
].reduce((r, k) => {r[k] = jest.fn(); return r;}, {});
const store = [
"getRoom", "getRooms", "getUser", "getSyncToken", "scrollback",
"save", "wantsSave", "setSyncToken", "storeEvents", "storeRoom",
"storeUser", "getFilterIdByName", "setFilterIdByName", "getFilter",
"storeFilter", "getSyncAccumulator", "startup", "deleteAllData",
].reduce((r, k) => {r[k] = jest.fn(); return r;}, {});
store.getSavedSync = jest.fn().mockReturnValue(Promise.resolve(null));
store.getSavedSyncToken = jest.fn().mockReturnValue(Promise.resolve(null));
store.setSyncData = jest.fn().mockReturnValue(Promise.resolve(null));
return new MatrixClient({
baseUrl: "https://my.home.server",
idBaseUrl: "https://identity.server",
accessToken: "my.access.token",
request: function() {}, // NOP
store: store,
scheduler: scheduler,
userId: "@alice:bar",
deviceId: "device",
sessionStore: sessionStore,
cryptoStore: cryptoStore,
cryptoCallbacks: { getCrossSigningKey, saveCrossSigningKeys },
});
}
describe("MegolmBackup", function() {
if (!global.Olm) {
logger.warn('Not running megolm backup unit tests: libolm not present');
return;
}
beforeAll(function() {
return Olm.init();
});
let olmDevice;
let mockOlmLib;
let mockCrypto;
let mockStorage;
let sessionStore;
let cryptoStore;
let megolmDecryption;
beforeEach(async function() {
mockCrypto = testUtils.mock(Crypto, 'Crypto');
mockCrypto.backupKey = new Olm.PkEncryption();
mockCrypto.backupKey.set_recipient_key(
"hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo",
);
mockCrypto.backupInfo = BACKUP_INFO;
mockStorage = new MockStorageApi();
sessionStore = new WebStorageSessionStore(mockStorage);
cryptoStore = new MemoryCryptoStore(mockStorage);
olmDevice = new OlmDevice(cryptoStore);
// we stub out the olm encryption bits
mockOlmLib = {};
mockOlmLib.ensureOlmSessionsForDevices = jest.fn();
mockOlmLib.encryptMessageForDevice =
jest.fn().mockResolvedValue(undefined);
});
describe("backup", function() {
let mockBaseApis;
let realSetTimeout;
beforeEach(function() {
mockBaseApis = {};
megolmDecryption = new MegolmDecryption({
userId: '@user:id',
crypto: mockCrypto,
olmDevice: olmDevice,
baseApis: mockBaseApis,
roomId: ROOM_ID,
});
megolmDecryption.olmlib = mockOlmLib;
// clobber the setTimeout function to run 100x faster.
// ideally we would use lolex, but we have no oportunity
// to tick the clock between the first try and the retry.
realSetTimeout = global.setTimeout;
global.setTimeout = function(f, n) {
return realSetTimeout(f, n/100);
};
});
afterEach(function() {
global.setTimeout = realSetTimeout;
});
it('automatically calls the key back up', function() {
const groupSession = new Olm.OutboundGroupSession();
groupSession.create();
// construct a fake decrypted key event via the use of a mocked
// 'crypto' implementation.
const event = new MatrixEvent({
type: 'm.room.encrypted',
});
const decryptedData = {
clearEvent: {
type: 'm.room_key',
content: {
algorithm: 'm.megolm.v1.aes-sha2',
room_id: ROOM_ID,
session_id: groupSession.session_id(),
session_key: groupSession.session_key(),
},
},
senderCurve25519Key: "SENDER_CURVE25519",
claimedEd25519Key: "SENDER_ED25519",
};
mockCrypto.decryptEvent = function() {
return Promise.resolve(decryptedData);
};
mockCrypto.cancelRoomKeyRequest = function() {};
mockCrypto.backupGroupSession = jest.fn();
return event.attemptDecryption(mockCrypto).then(() => {
return megolmDecryption.onRoomKeyEvent(event);
}).then(() => {
expect(mockCrypto.backupGroupSession).toHaveBeenCalled();
});
});
it('sends backups to the server', function() {
const groupSession = new Olm.OutboundGroupSession();
groupSession.create();
const ibGroupSession = new Olm.InboundGroupSession();
ibGroupSession.create(groupSession.session_key());
const client = makeTestClient(sessionStore, cryptoStore);
megolmDecryption = new MegolmDecryption({
userId: '@user:id',
crypto: mockCrypto,
olmDevice: olmDevice,
baseApis: client,
roomId: ROOM_ID,
});
megolmDecryption.olmlib = mockOlmLib;
return client.initCrypto()
.then(() => {
return cryptoStore.doTxn(
"readwrite",
[cryptoStore.STORE_SESSION],
(txn) => {
cryptoStore.addEndToEndInboundGroupSession(
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
groupSession.session_id(),
{
forwardingCurve25519KeyChain: undefined,
keysClaimed: {
ed25519: "SENDER_ED25519",
},
room_id: ROOM_ID,
session: ibGroupSession.pickle(olmDevice._pickleKey),
},
txn);
});
})
.then(() => {
client.enableKeyBackup({
algorithm: "m.megolm_backup.v1",
version: 1,
auth_data: {
public_key: "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo",
},
});
let numCalls = 0;
return new Promise((resolve, reject) => {
client._http.authedRequest = function(
callback, method, path, queryParams, data, opts,
) {
++numCalls;
expect(numCalls).toBeLessThanOrEqual(1);
if (numCalls >= 2) {
// exit out of retry loop if there's something wrong
reject(new Error("authedRequest called too many timmes"));
return Promise.resolve({});
}
expect(method).toBe("PUT");
expect(path).toBe("/room_keys/keys");
expect(queryParams.version).toBe(1);
expect(data.rooms[ROOM_ID].sessions).toBeDefined();
expect(data.rooms[ROOM_ID].sessions).toHaveProperty(
groupSession.session_id(),
);
resolve();
return Promise.resolve({});
};
client._crypto.backupGroupSession(
"roomId",
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
[],
groupSession.session_id(),
groupSession.session_key(),
);
}).then(() => {
expect(numCalls).toBe(1);
});
});
});
it('signs backups with the cross-signing master key', async function() {
const groupSession = new Olm.OutboundGroupSession();
groupSession.create();
const ibGroupSession = new Olm.InboundGroupSession();
ibGroupSession.create(groupSession.session_key());
const client = makeTestClient(sessionStore, cryptoStore);
megolmDecryption = new MegolmDecryption({
userId: '@user:id',
crypto: mockCrypto,
olmDevice: olmDevice,
baseApis: client,
roomId: ROOM_ID,
});
megolmDecryption.olmlib = mockOlmLib;
await client.initCrypto();
let privateKeys;
client.uploadDeviceSigningKeys = async function(e) {return;};
client.uploadKeySignatures = async function(e) {return;};
client.on("crossSigning.saveCrossSigningKeys", function(e) {
privateKeys = e;
});
client.on("crossSigning.getKey", function(e) {
e.done(privateKeys[e.type]);
});
await client.resetCrossSigningKeys();
let numCalls = 0;
await new Promise((resolve, reject) => {
client._http.authedRequest = function(
callback, method, path, queryParams, data, opts,
) {
++numCalls;
expect(numCalls).toBeLessThanOrEqual(1);
if (numCalls >= 2) {
// exit out of retry loop if there's something wrong
reject(new Error("authedRequest called too many timmes"));
return Promise.resolve({});
}
expect(method).toBe("POST");
expect(path).toBe("/room_keys/version");
try {
// make sure auth_data is signed by the master key
olmlib.pkVerify(
data.auth_data, client.getCrossSigningId(), "@alice:bar",
);
} catch (e) {
reject(e);
return Promise.resolve({});
}
resolve();
return Promise.resolve({});
};
client.createKeyBackupVersion({
algorithm: "m.megolm_backup.v1",
auth_data: {
public_key: "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo",
},
});
});
expect(numCalls).toBe(1);
});
it('retries when a backup fails', function() {
const groupSession = new Olm.OutboundGroupSession();
groupSession.create();
const ibGroupSession = new Olm.InboundGroupSession();
ibGroupSession.create(groupSession.session_key());
const scheduler = [
"getQueueForEvent", "queueEvent", "removeEventFromQueue",
"setProcessFunction",
].reduce((r, k) => {r[k] = jest.fn(); return r;}, {});
const store = [
"getRoom", "getRooms", "getUser", "getSyncToken", "scrollback",
"save", "wantsSave", "setSyncToken", "storeEvents", "storeRoom",
"storeUser", "getFilterIdByName", "setFilterIdByName", "getFilter",
"storeFilter", "getSyncAccumulator", "startup", "deleteAllData",
].reduce((r, k) => {r[k] = jest.fn(); return r;}, {});
store.getSavedSync = jest.fn().mockReturnValue(Promise.resolve(null));
store.getSavedSyncToken = jest.fn().mockReturnValue(Promise.resolve(null));
store.setSyncData = jest.fn().mockReturnValue(Promise.resolve(null));
const client = new MatrixClient({
baseUrl: "https://my.home.server",
idBaseUrl: "https://identity.server",
accessToken: "my.access.token",
request: function() {}, // NOP
store: store,
scheduler: scheduler,
userId: "@alice:bar",
deviceId: "device",
sessionStore: sessionStore,
cryptoStore: cryptoStore,
});
megolmDecryption = new MegolmDecryption({
userId: '@user:id',
crypto: mockCrypto,
olmDevice: olmDevice,
baseApis: client,
roomId: ROOM_ID,
});
megolmDecryption.olmlib = mockOlmLib;
return client.initCrypto()
.then(() => {
return cryptoStore.doTxn(
"readwrite",
[cryptoStore.STORE_SESSION],
(txn) => {
cryptoStore.addEndToEndInboundGroupSession(
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
groupSession.session_id(),
{
forwardingCurve25519KeyChain: undefined,
keysClaimed: {
ed25519: "SENDER_ED25519",
},
room_id: ROOM_ID,
session: ibGroupSession.pickle(olmDevice._pickleKey),
},
txn);
});
})
.then(() => {
client.enableKeyBackup({
algorithm: "foobar",
version: 1,
auth_data: {
public_key: "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo",
},
});
let numCalls = 0;
return new Promise((resolve, reject) => {
client._http.authedRequest = function(
callback, method, path, queryParams, data, opts,
) {
++numCalls;
expect(numCalls).toBeLessThanOrEqual(2);
if (numCalls >= 3) {
// exit out of retry loop if there's something wrong
reject(new Error("authedRequest called too many timmes"));
return Promise.resolve({});
}
expect(method).toBe("PUT");
expect(path).toBe("/room_keys/keys");
expect(queryParams.version).toBe(1);
expect(data.rooms[ROOM_ID].sessions).toBeDefined();
expect(data.rooms[ROOM_ID].sessions).toHaveProperty(
groupSession.session_id(),
);
if (numCalls > 1) {
resolve();
return Promise.resolve({});
} else {
return Promise.reject(
new Error("this is an expected failure"),
);
}
};
client._crypto.backupGroupSession(
"roomId",
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
[],
groupSession.session_id(),
groupSession.session_key(),
);
}).then(() => {
expect(numCalls).toBe(2);
});
});
});
});
describe("restore", function() {
let client;
beforeEach(function() {
client = makeTestClient(sessionStore, cryptoStore);
megolmDecryption = new MegolmDecryption({
userId: '@user:id',
crypto: mockCrypto,
olmDevice: olmDevice,
baseApis: client,
roomId: ROOM_ID,
});
megolmDecryption.olmlib = mockOlmLib;
return client.initCrypto();
});
afterEach(function() {
client.stopClient();
});
it('can restore from backup', function() {
client._http.authedRequest = function() {
return Promise.resolve(KEY_BACKUP_DATA);
};
return client.restoreKeyBackupWithRecoveryKey(
"EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d",
ROOM_ID,
SESSION_ID,
BACKUP_INFO,
).then(() => {
return megolmDecryption.decryptEvent(ENCRYPTED_EVENT);
}).then((res) => {
expect(res.clearEvent.content).toEqual('testytest');
});
});
it('can restore backup by room', function() {
client._http.authedRequest = function() {
return Promise.resolve({
rooms: {
[ROOM_ID]: {
sessions: {
[SESSION_ID]: KEY_BACKUP_DATA,
},
},
},
});
};
return client.restoreKeyBackupWithRecoveryKey(
"EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d",
null, null, BACKUP_INFO,
).then(() => {
return megolmDecryption.decryptEvent(ENCRYPTED_EVENT);
}).then((res) => {
expect(res.clearEvent.content).toEqual('testytest');
});
});
it('has working cache functions', async function() {
const key = Uint8Array.from([1, 2, 3, 4, 5, 6, 7, 8]);
await client._crypto.storeSessionBackupPrivateKey(key);
const result = await client._crypto.getSessionBackupPrivateKey();
expect(new Uint8Array(result)).toEqual(key);
});
it('caches session backup keys as it encounters them', async function() {
const cachedNull = await client._crypto.getSessionBackupPrivateKey();
expect(cachedNull).toBeNull();
client._http.authedRequest = function() {
return Promise.resolve(KEY_BACKUP_DATA);
};
await new Promise((resolve) => {
client.restoreKeyBackupWithRecoveryKey(
"EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d",
ROOM_ID,
SESSION_ID,
BACKUP_INFO,
{ cacheCompleteCallback: resolve },
);
});
const cachedKey = await client._crypto.getSessionBackupPrivateKey();
expect(cachedKey).not.toBeNull();
});
});
});
+797
View File
@@ -0,0 +1,797 @@
/*
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.
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 '../../olm-loader';
import anotherjson from 'another-json';
import * as olmlib from "../../../src/crypto/olmlib";
import {TestClient} from '../../TestClient';
import {HttpResponse, setHttpResponses} from '../../test-utils';
async function makeTestClient(userInfo, options, keys) {
if (!keys) keys = {};
function getCrossSigningKey(type) {
return keys[type];
}
function saveCrossSigningKeys(k) {
Object.assign(keys, k);
}
if (!options) options = {};
options.cryptoCallbacks = Object.assign(
{}, { getCrossSigningKey, saveCrossSigningKeys }, options.cryptoCallbacks || {},
);
const client = (new TestClient(
userInfo.userId, userInfo.deviceId, undefined, undefined, options,
)).client;
await client.initCrypto();
return client;
}
describe("Cross Signing", function() {
if (!global.Olm) {
console.warn('Not running megolm backup unit tests: libolm not present');
return;
}
beforeAll(function() {
return global.Olm.init();
});
it("should sign the master key with the device key", async function() {
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
);
alice.uploadDeviceSigningKeys = jest.fn(async (auth, keys) => {
await olmlib.verifySignature(
alice._crypto._olmDevice, keys.master_key, "@alice:example.com",
"Osborne2", alice._crypto._olmDevice.deviceEd25519Key,
);
});
alice.uploadKeySignatures = async () => {};
// set Alice's cross-signing key
await alice.resetCrossSigningKeys();
expect(alice.uploadDeviceSigningKeys).toHaveBeenCalled();
});
it("should upload a signature when a user is verified", async function() {
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
);
alice.uploadDeviceSigningKeys = async () => {};
alice.uploadKeySignatures = async () => {};
// set Alice's cross-signing key
await alice.resetCrossSigningKeys();
// Alice downloads Bob's device key
alice._crypto._deviceList.storeCrossSigningForUser("@bob:example.com", {
keys: {
master: {
user_id: "@bob:example.com",
usage: ["master"],
keys: {
"ed25519:bobs+master+pubkey": "bobs+master+pubkey",
},
},
},
});
// Alice verifies Bob's key
const promise = new Promise((resolve, reject) => {
alice.uploadKeySignatures = (...args) => {
resolve(...args);
};
});
await alice.setDeviceVerified("@bob:example.com", "bobs+master+pubkey", true);
// Alice should send a signature of Bob's key to the server
await promise;
});
it("should get cross-signing keys from sync", async function() {
const masterKey = new Uint8Array([
0xda, 0x5a, 0x27, 0x60, 0xe3, 0x3a, 0xc5, 0x82,
0x9d, 0x12, 0xc3, 0xbe, 0xe8, 0xaa, 0xc2, 0xef,
0xae, 0xb1, 0x05, 0xc1, 0xe7, 0x62, 0x78, 0xa6,
0xd7, 0x1f, 0xf8, 0x2c, 0x51, 0x85, 0xf0, 0x1d,
]);
const selfSigningKey = new Uint8Array([
0x1e, 0xf4, 0x01, 0x6d, 0x4f, 0xa1, 0x73, 0x66,
0x6b, 0xf8, 0x93, 0xf5, 0xb0, 0x4d, 0x17, 0xc0,
0x17, 0xb5, 0xa5, 0xf6, 0x59, 0x11, 0x8b, 0x49,
0x34, 0xf2, 0x4b, 0x64, 0x9b, 0x52, 0xf8, 0x5f,
]);
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
{
cryptoCallbacks: {
// will be called to sign our own device
getCrossSigningKey: type => {
if (type === 'master') {
return masterKey;
} else {
return selfSigningKey;
}
},
},
},
);
const keyChangePromise = new Promise((resolve, reject) => {
alice.once("crossSigning.keysChanged", async (e) => {
resolve(e);
await alice.checkOwnCrossSigningTrust();
});
});
const uploadSigsPromise = new Promise((resolve, reject) => {
alice.uploadKeySignatures = jest.fn(async (content) => {
await olmlib.verifySignature(
alice._crypto._olmDevice,
content["@alice:example.com"][
"nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk"
],
"@alice:example.com",
"Osborne2", alice._crypto._olmDevice.deviceEd25519Key,
);
olmlib.pkVerify(
content["@alice:example.com"]["Osborne2"],
"EmkqvokUn8p+vQAGZitOk4PWjp7Ukp3txV2TbMPEiBQ",
"@alice:example.com",
);
resolve();
});
});
const deviceInfo = alice._crypto._deviceList._devices["@alice:example.com"]
.Osborne2;
const aliceDevice = {
user_id: "@alice:example.com",
device_id: "Osborne2",
};
aliceDevice.keys = deviceInfo.keys;
aliceDevice.algorithms = deviceInfo.algorithms;
await alice._crypto._signObject(aliceDevice);
olmlib.pkSign(aliceDevice, selfSigningKey, "@alice:example.com");
// feed sync result that includes master key, ssk, device key
const responses = [
HttpResponse.PUSH_RULES_RESPONSE,
{
method: "POST",
path: "/keys/upload",
data: {
one_time_key_counts: {
curve25519: 100,
signed_curve25519: 100,
},
},
},
HttpResponse.filterResponse("@alice:example.com"),
{
method: "GET",
path: "/sync",
data: {
next_batch: "abcdefg",
device_lists: {
changed: [
"@alice:example.com",
"@bob:example.com",
],
},
},
},
{
method: "POST",
path: "/keys/query",
data: {
"failures": {},
"device_keys": {
"@alice:example.com": {
"Osborne2": aliceDevice,
},
},
"master_keys": {
"@alice:example.com": {
user_id: "@alice:example.com",
usage: ["master"],
keys: {
"ed25519:nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk":
"nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk",
},
},
},
"self_signing_keys": {
"@alice:example.com": {
user_id: "@alice:example.com",
usage: ["self-signing"],
keys: {
"ed25519:EmkqvokUn8p+vQAGZitOk4PWjp7Ukp3txV2TbMPEiBQ":
"EmkqvokUn8p+vQAGZitOk4PWjp7Ukp3txV2TbMPEiBQ",
},
signatures: {
"@alice:example.com": {
"ed25519:nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk":
"Wqx/HXR851KIi8/u/UX+fbAMtq9Uj8sr8FsOcqrLfVYa6lAmbXs"
+ "Vhfy4AlZ3dnEtjgZx0U0QDrghEn2eYBeOCA",
},
},
},
},
},
},
{
method: "POST",
path: "/keys/upload",
data: {
one_time_key_counts: {
curve25519: 100,
signed_curve25519: 100,
},
},
},
];
setHttpResponses(alice, responses, true, true);
await alice.startClient();
// once ssk is confirmed, device key should be trusted
await keyChangePromise;
await uploadSigsPromise;
const aliceTrust = alice.checkUserTrust("@alice:example.com");
expect(aliceTrust.isCrossSigningVerified()).toBeTruthy();
expect(aliceTrust.isTofu()).toBeTruthy();
expect(aliceTrust.isVerified()).toBeTruthy();
const aliceDeviceTrust = alice.checkDeviceTrust("@alice:example.com", "Osborne2");
expect(aliceDeviceTrust.isCrossSigningVerified()).toBeTruthy();
expect(aliceDeviceTrust.isLocallyVerified()).toBeTruthy();
expect(aliceDeviceTrust.isTofu()).toBeTruthy();
expect(aliceDeviceTrust.isVerified()).toBeTruthy();
});
it("should use trust chain to determine device verification", async function() {
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
);
alice.uploadDeviceSigningKeys = async () => {};
alice.uploadKeySignatures = async () => {};
// set Alice's cross-signing key
await alice.resetCrossSigningKeys();
// Alice downloads Bob's ssk and device key
const bobMasterSigning = new global.Olm.PkSigning();
const bobMasterPrivkey = bobMasterSigning.generate_seed();
const bobMasterPubkey = bobMasterSigning.init_with_seed(bobMasterPrivkey);
const bobSigning = new global.Olm.PkSigning();
const bobPrivkey = bobSigning.generate_seed();
const bobPubkey = bobSigning.init_with_seed(bobPrivkey);
const bobSSK = {
user_id: "@bob:example.com",
usage: ["self_signing"],
keys: {
["ed25519:" + bobPubkey]: bobPubkey,
},
};
const sskSig = bobMasterSigning.sign(anotherjson.stringify(bobSSK));
bobSSK.signatures = {
"@bob:example.com": {
["ed25519:" + bobMasterPubkey]: sskSig,
},
};
alice._crypto._deviceList.storeCrossSigningForUser("@bob:example.com", {
keys: {
master: {
user_id: "@bob:example.com",
usage: ["master"],
keys: {
["ed25519:" + bobMasterPubkey]: bobMasterPubkey,
},
},
self_signing: bobSSK,
},
firstUse: 1,
unsigned: {},
});
const bobDevice = {
user_id: "@bob:example.com",
device_id: "Dynabook",
algorithms: ["m.olm.curve25519-aes-sha256", "m.megolm.v1.aes-sha"],
keys: {
"curve25519:Dynabook": "somePubkey",
"ed25519:Dynabook": "someOtherPubkey",
},
};
const sig = bobSigning.sign(anotherjson.stringify(bobDevice));
bobDevice.signatures = {
"@bob:example.com": {
["ed25519:" + bobPubkey]: sig,
},
};
alice._crypto._deviceList.storeDevicesForUser("@bob:example.com", {
Dynabook: bobDevice,
});
// Bob's device key should be TOFU
const bobTrust = alice.checkUserTrust("@bob:example.com");
expect(bobTrust.isVerified()).toBeFalsy();
expect(bobTrust.isTofu()).toBeTruthy();
const bobDeviceTrust = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
expect(bobDeviceTrust.isVerified()).toBeFalsy();
expect(bobDeviceTrust.isTofu()).toBeTruthy();
// Alice verifies Bob's SSK
alice.uploadKeySignatures = () => {};
await alice.setDeviceVerified("@bob:example.com", bobMasterPubkey, true);
// Bob's device key should be trusted
const bobTrust2 = alice.checkUserTrust("@bob:example.com");
expect(bobTrust2.isCrossSigningVerified()).toBeTruthy();
expect(bobTrust2.isTofu()).toBeTruthy();
const bobDeviceTrust2 = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
expect(bobDeviceTrust2.isCrossSigningVerified()).toBeTruthy();
expect(bobDeviceTrust2.isLocallyVerified()).toBeFalsy();
expect(bobDeviceTrust2.isTofu()).toBeTruthy();
});
it("should trust signatures received from other devices", async function() {
const aliceKeys = {};
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
null,
aliceKeys,
);
alice._crypto._deviceList.startTrackingDeviceList("@bob:example.com");
alice._crypto._deviceList.stopTrackingAllDeviceLists = () => {};
alice.uploadDeviceSigningKeys = async () => {};
alice.uploadKeySignatures = async () => {};
// set Alice's cross-signing key
await alice.resetCrossSigningKeys();
const selfSigningKey = new Uint8Array([
0x1e, 0xf4, 0x01, 0x6d, 0x4f, 0xa1, 0x73, 0x66,
0x6b, 0xf8, 0x93, 0xf5, 0xb0, 0x4d, 0x17, 0xc0,
0x17, 0xb5, 0xa5, 0xf6, 0x59, 0x11, 0x8b, 0x49,
0x34, 0xf2, 0x4b, 0x64, 0x9b, 0x52, 0xf8, 0x5f,
]);
const keyChangePromise = new Promise((resolve, reject) => {
alice._crypto._deviceList.once("userCrossSigningUpdated", (userId) => {
if (userId === "@bob:example.com") {
resolve();
}
});
});
const deviceInfo = alice._crypto._deviceList._devices["@alice:example.com"]
.Osborne2;
const aliceDevice = {
user_id: "@alice:example.com",
device_id: "Osborne2",
};
aliceDevice.keys = deviceInfo.keys;
aliceDevice.algorithms = deviceInfo.algorithms;
await alice._crypto._signObject(aliceDevice);
const bobOlmAccount = new global.Olm.Account();
bobOlmAccount.create();
const bobKeys = JSON.parse(bobOlmAccount.identity_keys());
const bobDevice = {
user_id: "@bob:example.com",
device_id: "Dynabook",
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
keys: {
"ed25519:Dynabook": bobKeys.ed25519,
"curve25519:Dynabook": bobKeys.curve25519,
},
};
const deviceStr = anotherjson.stringify(bobDevice);
bobDevice.signatures = {
"@bob:example.com": {
"ed25519:Dynabook": bobOlmAccount.sign(deviceStr),
},
};
olmlib.pkSign(bobDevice, selfSigningKey, "@bob:example.com");
const bobMaster = {
user_id: "@bob:example.com",
usage: ["master"],
keys: {
"ed25519:nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk":
"nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk",
},
};
olmlib.pkSign(bobMaster, aliceKeys.user_signing, "@alice:example.com");
// Alice downloads Bob's keys
// - device key
// - ssk
// - master key signed by her usk (pretend that it was signed by another
// of Alice's devices)
const responses = [
HttpResponse.PUSH_RULES_RESPONSE,
{
method: "POST",
path: "/keys/upload",
data: {
one_time_key_counts: {
curve25519: 100,
signed_curve25519: 100,
},
},
},
HttpResponse.filterResponse("@alice:example.com"),
{
method: "GET",
path: "/sync",
data: {
next_batch: "abcdefg",
device_lists: {
changed: [
"@bob:example.com",
],
},
},
},
{
method: "POST",
path: "/keys/query",
data: {
"failures": {},
"device_keys": {
"@alice:example.com": {
"Osborne2": aliceDevice,
},
"@bob:example.com": {
"Dynabook": bobDevice,
},
},
"master_keys": {
"@bob:example.com": bobMaster,
},
"self_signing_keys": {
"@bob:example.com": {
user_id: "@bob:example.com",
usage: ["self-signing"],
keys: {
"ed25519:EmkqvokUn8p+vQAGZitOk4PWjp7Ukp3txV2TbMPEiBQ":
"EmkqvokUn8p+vQAGZitOk4PWjp7Ukp3txV2TbMPEiBQ",
},
signatures: {
"@bob:example.com": {
"ed25519:nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk":
"2KLiufImvEbfJuAFvsaZD+PsL8ELWl7N1u9yr/9hZvwRghBfQMB"
+ "LAI86b1kDV9+Cq1lt85ykReeCEzmTEPY2BQ",
},
},
},
},
},
},
{
method: "POST",
path: "/keys/upload",
data: {
one_time_key_counts: {
curve25519: 100,
signed_curve25519: 100,
},
},
},
];
setHttpResponses(alice, responses);
await alice.startClient();
await keyChangePromise;
// Bob's device key should be trusted
const bobTrust = alice.checkUserTrust("@bob:example.com");
expect(bobTrust.isCrossSigningVerified()).toBeTruthy();
expect(bobTrust.isTofu()).toBeTruthy();
const bobDeviceTrust = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
expect(bobDeviceTrust.isCrossSigningVerified()).toBeTruthy();
expect(bobDeviceTrust.isLocallyVerified()).toBeFalsy();
expect(bobDeviceTrust.isTofu()).toBeTruthy();
});
it("should dis-trust an unsigned device", async function() {
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
);
alice.uploadDeviceSigningKeys = async () => {};
alice.uploadKeySignatures = async () => {};
// set Alice's cross-signing key
await alice.resetCrossSigningKeys();
// Alice downloads Bob's ssk and device key
// (NOTE: device key is not signed by ssk)
const bobMasterSigning = new global.Olm.PkSigning();
const bobMasterPrivkey = bobMasterSigning.generate_seed();
const bobMasterPubkey = bobMasterSigning.init_with_seed(bobMasterPrivkey);
const bobSigning = new global.Olm.PkSigning();
const bobPrivkey = bobSigning.generate_seed();
const bobPubkey = bobSigning.init_with_seed(bobPrivkey);
const bobSSK = {
user_id: "@bob:example.com",
usage: ["self_signing"],
keys: {
["ed25519:" + bobPubkey]: bobPubkey,
},
};
const sskSig = bobMasterSigning.sign(anotherjson.stringify(bobSSK));
bobSSK.signatures = {
"@bob:example.com": {
["ed25519:" + bobMasterPubkey]: sskSig,
},
};
alice._crypto._deviceList.storeCrossSigningForUser("@bob:example.com", {
keys: {
master: {
user_id: "@bob:example.com",
usage: ["master"],
keys: {
["ed25519:" + bobMasterPubkey]: bobMasterPubkey,
},
},
self_signing: bobSSK,
},
firstUse: 1,
unsigned: {},
});
const bobDevice = {
user_id: "@bob:example.com",
device_id: "Dynabook",
algorithms: ["m.olm.curve25519-aes-sha256", "m.megolm.v1.aes-sha"],
keys: {
"curve25519:Dynabook": "somePubkey",
"ed25519:Dynabook": "someOtherPubkey",
},
};
alice._crypto._deviceList.storeDevicesForUser("@bob:example.com", {
Dynabook: bobDevice,
});
// Bob's device key should be untrusted
const bobDeviceTrust = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
expect(bobDeviceTrust.isVerified()).toBeFalsy();
expect(bobDeviceTrust.isTofu()).toBeFalsy();
// Alice verifies Bob's SSK
await alice.setDeviceVerified("@bob:example.com", bobMasterPubkey, true);
// Bob's device key should be untrusted
const bobDeviceTrust2 = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
expect(bobDeviceTrust2.isVerified()).toBeFalsy();
expect(bobDeviceTrust2.isTofu()).toBeFalsy();
});
it("should dis-trust a user when their ssk changes", async function() {
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
);
alice.uploadDeviceSigningKeys = async () => {};
alice.uploadKeySignatures = async () => {};
await alice.resetCrossSigningKeys();
// Alice downloads Bob's keys
const bobMasterSigning = new global.Olm.PkSigning();
const bobMasterPrivkey = bobMasterSigning.generate_seed();
const bobMasterPubkey = bobMasterSigning.init_with_seed(bobMasterPrivkey);
const bobSigning = new global.Olm.PkSigning();
const bobPrivkey = bobSigning.generate_seed();
const bobPubkey = bobSigning.init_with_seed(bobPrivkey);
const bobSSK = {
user_id: "@bob:example.com",
usage: ["self_signing"],
keys: {
["ed25519:" + bobPubkey]: bobPubkey,
},
};
const sskSig = bobMasterSigning.sign(anotherjson.stringify(bobSSK));
bobSSK.signatures = {
"@bob:example.com": {
["ed25519:" + bobMasterPubkey]: sskSig,
},
};
alice._crypto._deviceList.storeCrossSigningForUser("@bob:example.com", {
keys: {
master: {
user_id: "@bob:example.com",
usage: ["master"],
keys: {
["ed25519:" + bobMasterPubkey]: bobMasterPubkey,
},
},
self_signing: bobSSK,
},
firstUse: 1,
unsigned: {},
});
const bobDevice = {
user_id: "@bob:example.com",
device_id: "Dynabook",
algorithms: ["m.olm.curve25519-aes-sha256", "m.megolm.v1.aes-sha"],
keys: {
"curve25519:Dynabook": "somePubkey",
"ed25519:Dynabook": "someOtherPubkey",
},
};
const bobDeviceString = anotherjson.stringify(bobDevice);
const sig = bobSigning.sign(bobDeviceString);
bobDevice.signatures = {};
bobDevice.signatures["@bob:example.com"] = {};
bobDevice.signatures["@bob:example.com"]["ed25519:" + bobPubkey] = sig;
alice._crypto._deviceList.storeDevicesForUser("@bob:example.com", {
Dynabook: bobDevice,
});
// Alice verifies Bob's SSK
alice.uploadKeySignatures = () => {};
await alice.setDeviceVerified("@bob:example.com", bobMasterPubkey, true);
// Bob's device key should be trusted
const bobDeviceTrust = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
expect(bobDeviceTrust.isVerified()).toBeTruthy();
expect(bobDeviceTrust.isTofu()).toBeTruthy();
// Alice downloads new SSK for Bob
const bobMasterSigning2 = new global.Olm.PkSigning();
const bobMasterPrivkey2 = bobMasterSigning2.generate_seed();
const bobMasterPubkey2 = bobMasterSigning2.init_with_seed(bobMasterPrivkey2);
const bobSigning2 = new global.Olm.PkSigning();
const bobPrivkey2 = bobSigning2.generate_seed();
const bobPubkey2 = bobSigning2.init_with_seed(bobPrivkey2);
const bobSSK2 = {
user_id: "@bob:example.com",
usage: ["self_signing"],
keys: {
["ed25519:" + bobPubkey2]: bobPubkey2,
},
};
const sskSig2 = bobMasterSigning2.sign(anotherjson.stringify(bobSSK2));
bobSSK2.signatures = {
"@bob:example.com": {
["ed25519:" + bobMasterPubkey2]: sskSig2,
},
};
alice._crypto._deviceList.storeCrossSigningForUser("@bob:example.com", {
keys: {
master: {
user_id: "@bob:example.com",
usage: ["master"],
keys: {
["ed25519:" + bobMasterPubkey2]: bobMasterPubkey2,
},
},
self_signing: bobSSK2,
},
firstUse: 0,
unsigned: {},
});
// Bob's and his device should be untrusted
const bobTrust = alice.checkUserTrust("@bob:example.com");
expect(bobTrust.isVerified()).toBeFalsy();
expect(bobTrust.isTofu()).toBeFalsy();
const bobDeviceTrust2 = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
expect(bobDeviceTrust2.isVerified()).toBeFalsy();
expect(bobDeviceTrust2.isTofu()).toBeFalsy();
// Alice verifies Bob's SSK
alice.uploadKeySignatures = () => {};
await alice.setDeviceVerified("@bob:example.com", bobMasterPubkey2, true);
// Bob should be trusted but not his device
const bobTrust2 = alice.checkUserTrust("@bob:example.com");
expect(bobTrust2.isVerified()).toBeTruthy();
const bobDeviceTrust3 = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
expect(bobDeviceTrust3.isVerified()).toBeFalsy();
// Alice gets new signature for device
const sig2 = bobSigning2.sign(bobDeviceString);
bobDevice.signatures["@bob:example.com"]["ed25519:" + bobPubkey2] = sig2;
alice._crypto._deviceList.storeDevicesForUser("@bob:example.com", {
Dynabook: bobDevice,
});
// Bob's device should be trusted again (but not TOFU)
const bobTrust3 = alice.checkUserTrust("@bob:example.com");
expect(bobTrust3.isVerified()).toBeTruthy();
const bobDeviceTrust4 = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
expect(bobDeviceTrust4.isCrossSigningVerified()).toBeTruthy();
});
it("should offer to upgrade device verifications to cross-signing", async function() {
let upgradeResolveFunc;
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
{
cryptoCallbacks: {
shouldUpgradeDeviceVerifications: (verifs) => {
expect(verifs.users["@bob:example.com"]).toBeDefined();
upgradeResolveFunc();
return ["@bob:example.com"];
},
},
},
);
const bob = await makeTestClient(
{userId: "@bob:example.com", deviceId: "Dynabook"},
);
bob.uploadDeviceSigningKeys = async () => {};
bob.uploadKeySignatures = async () => {};
// set Bob's cross-signing key
await bob.resetCrossSigningKeys();
alice._crypto._deviceList.storeDevicesForUser("@bob:example.com", {
Dynabook: {
algorithms: ["m.olm.curve25519-aes-sha256", "m.megolm.v1.aes-sha"],
keys: {
"curve25519:Dynabook": bob._crypto._olmDevice.deviceCurve25519Key,
"ed25519:Dynabook": bob._crypto._olmDevice.deviceEd25519Key,
},
verified: 1,
known: true,
},
});
alice._crypto._deviceList.storeCrossSigningForUser(
"@bob:example.com",
bob._crypto._crossSigningInfo.toStorage(),
);
alice.uploadDeviceSigningKeys = async () => {};
alice.uploadKeySignatures = async () => {};
// when alice sets up cross-signing, she should notice that bob's
// cross-signing key is signed by his Dynabook, which alice has
// verified, and ask if the device verification should be upgraded to a
// cross-signing verification
let upgradePromise = new Promise((resolve) => {
upgradeResolveFunc = resolve;
});
await alice.resetCrossSigningKeys();
await upgradePromise;
const bobTrust = alice.checkUserTrust("@bob:example.com");
expect(bobTrust.isCrossSigningVerified()).toBeTruthy();
expect(bobTrust.isTofu()).toBeTruthy();
// "forget" that Bob is trusted
delete alice._crypto._deviceList._crossSigningInfo["@bob:example.com"]
.keys.master.signatures["@alice:example.com"];
const bobTrust2 = alice.checkUserTrust("@bob:example.com");
expect(bobTrust2.isCrossSigningVerified()).toBeFalsy();
expect(bobTrust2.isTofu()).toBeTruthy();
upgradePromise = new Promise((resolve) => {
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");
expect(bobTrust3.isCrossSigningVerified()).toBeTruthy();
expect(bobTrust3.isTofu()).toBeTruthy();
});
});
@@ -0,0 +1,87 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
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 {
IndexedDBCryptoStore,
} from '../../../src/crypto/store/indexeddb-crypto-store';
import {MemoryCryptoStore} from '../../../src/crypto/store/memory-crypto-store';
import 'fake-indexeddb/auto';
import 'jest-localstorage-mock';
import {
ROOM_KEY_REQUEST_STATES,
} from '../../../src/crypto/OutgoingRoomKeyRequestManager';
const requests = [
{
requestId: "A",
requestBody: { session_id: "A", room_id: "A" },
state: ROOM_KEY_REQUEST_STATES.SENT,
},
{
requestId: "B",
requestBody: { session_id: "B", room_id: "B" },
state: ROOM_KEY_REQUEST_STATES.SENT,
},
{
requestId: "C",
requestBody: { session_id: "C", room_id: "C" },
state: ROOM_KEY_REQUEST_STATES.UNSENT,
},
];
describe.each([
["IndexedDBCryptoStore",
() => new IndexedDBCryptoStore(global.indexedDB, "tests")],
["LocalStorageCryptoStore",
() => new IndexedDBCryptoStore(undefined, "tests")],
["MemoryCryptoStore", () => {
const store = new IndexedDBCryptoStore(undefined, "tests");
store._backend = new MemoryCryptoStore();
store._backendPromise = Promise.resolve(store._backend);
return store;
}],
])("Outgoing room key requests [%s]", function(name, dbFactory) {
let store;
beforeAll(async () => {
store = dbFactory();
await store.startup();
await Promise.all(requests.map((request) =>
store.getOrAddOutgoingRoomKeyRequest(request),
));
});
it("getAllOutgoingRoomKeyRequestsByState retrieves all entries in a given state",
async () => {
const r = await
store.getAllOutgoingRoomKeyRequestsByState(ROOM_KEY_REQUEST_STATES.SENT);
expect(r).toHaveLength(2);
requests.filter((e) => e.state == ROOM_KEY_REQUEST_STATES.SENT).forEach((e) => {
expect(r).toContainEqual(e);
});
});
test("getOutgoingRoomKeyRequestByState retrieves any entry in a given state",
async () => {
const r =
await store.getOutgoingRoomKeyRequestByState([ROOM_KEY_REQUEST_STATES.SENT]);
expect(r).not.toBeNull();
expect(r).not.toBeUndefined();
expect(r.state).toEqual(ROOM_KEY_REQUEST_STATES.SENT);
expect(requests).toContainEqual(r);
});
});
+662
View File
@@ -0,0 +1,662 @@
/*
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 '../../olm-loader';
import * as olmlib from "../../../src/crypto/olmlib";
import {SECRET_STORAGE_ALGORITHM_V1_AES} from "../../../src/crypto/SecretStorage";
import {MatrixEvent} from "../../../src/models/event";
import {TestClient} from '../../TestClient';
import {makeTestClients} from './verification/util';
import {encryptAES} from "../../../src/crypto/aes";
import * as utils from "../../../src/utils";
try {
const crypto = require('crypto');
utils.setCrypto(crypto);
} catch (err) {
console.log('nodejs was compiled without crypto support');
}
async function makeTestClient(userInfo, options) {
const client = (new TestClient(
userInfo.userId, userInfo.deviceId, undefined, undefined, options,
)).client;
// Make it seem as if we've synced and thus the store can be trusted to
// contain valid account data.
client.isInitialSyncComplete = function() {
return true;
};
await client.initCrypto();
// No need to download keys for these tests
client._crypto.downloadKeys = async function() {};
return client;
}
// Wrapper around pkSign to return a signed object. pkSign returns the
// signature, rather than the signed object.
function sign(obj, key, userId) {
olmlib.pkSign(obj, key, userId);
return obj;
}
describe("Secrets", function() {
if (!global.Olm) {
console.warn('Not running megolm backup unit tests: libolm not present');
return;
}
beforeAll(function() {
return global.Olm.init();
});
it("should store and retrieve a secret", async function() {
const key = new Uint8Array(16);
for (let i = 0; i < 16; i++) key[i] = i;
const signing = new global.Olm.PkSigning();
const signingKey = signing.generate_seed();
const signingPubKey = signing.init_with_seed(signingKey);
const signingkeyInfo = {
user_id: "@alice:example.com",
usage: ['master'],
keys: {
['ed25519:' + signingPubKey]: signingPubKey,
},
};
const getKey = jest.fn(e => {
expect(Object.keys(e.keys)).toEqual(["abc"]);
return ['abc', key];
});
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
{
cryptoCallbacks: {
getCrossSigningKey: t => signingKey,
getSecretStorageKey: getKey,
},
},
);
alice._crypto._crossSigningInfo.setKeys({
master: signingkeyInfo,
});
const secretStorage = alice._crypto._secretStorage;
alice.setAccountData = async function(eventType, contents, callback) {
alice.store.storeAccountDataEvents([
new MatrixEvent({
type: eventType,
content: contents,
}),
]);
if (callback) {
callback();
}
};
const keyAccountData = {
algorithm: SECRET_STORAGE_ALGORITHM_V1_AES,
};
await alice._crypto._crossSigningInfo.signObject(keyAccountData, 'master');
alice.store.storeAccountDataEvents([
new MatrixEvent({
type: "m.secret_storage.key.abc",
content: keyAccountData,
}),
]);
expect(await secretStorage.isStored("foo")).toBeFalsy();
await secretStorage.store("foo", "bar", ["abc"]);
expect(await secretStorage.isStored("foo")).toBeTruthy();
expect(await secretStorage.get("foo")).toBe("bar");
expect(getKey).toHaveBeenCalled();
});
it("should throw if given a key that doesn't exist", async function() {
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
);
try {
await alice.storeSecret("foo", "bar", ["this secret does not exist"]);
// should be able to use expect(...).toThrow() but mocha still fails
// the test even when it throws for reasons I have no inclination to debug
expect(true).toBeFalsy();
} catch (e) {
}
});
it("should refuse to encrypt with zero keys", async function() {
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
);
try {
await alice.storeSecret("foo", "bar", []);
expect(true).toBeFalsy();
} catch (e) {
}
});
it("should encrypt with default key if keys is null", async function() {
const key = new Uint8Array(16);
for (let i = 0; i < 16; i++) key[i] = i;
const getKey = jest.fn(e => {
expect(Object.keys(e.keys)).toEqual([newKeyId]);
return [newKeyId, key];
});
let keys = {};
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
{
cryptoCallbacks: {
getCrossSigningKey: t => keys[t],
saveCrossSigningKeys: k => keys = k,
getSecretStorageKey: getKey,
},
},
);
alice.setAccountData = async function(eventType, contents, callback) {
alice.store.storeAccountDataEvents([
new MatrixEvent({
type: eventType,
content: contents,
}),
]);
};
alice.resetCrossSigningKeys();
const newKeyId = await alice.addSecretStorageKey(
SECRET_STORAGE_ALGORITHM_V1_AES,
);
// we don't await on this because it waits for the event to come down the sync
// which won't happen in the test setup
alice.setDefaultSecretStorageKeyId(newKeyId);
await alice.storeSecret("foo", "bar");
const accountData = alice.getAccountData('foo');
expect(accountData.getContent().encrypted).toBeTruthy();
});
it("should refuse to encrypt if no keys given and no default key", async function() {
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
);
try {
await alice.storeSecret("foo", "bar");
expect(true).toBeFalsy();
} catch (e) {
}
});
it("should request secrets from other clients", async function() {
const [osborne2, vax] = await makeTestClients(
[
{userId: "@alice:example.com", deviceId: "Osborne2"},
{userId: "@alice:example.com", deviceId: "VAX"},
],
{
cryptoCallbacks: {
onSecretRequested: e => {
expect(e.name).toBe("foo");
return "bar";
},
},
},
);
const vaxDevice = vax.client._crypto._olmDevice;
const osborne2Device = osborne2.client._crypto._olmDevice;
const secretStorage = osborne2.client._crypto._secretStorage;
osborne2.client._crypto._deviceList.storeDevicesForUser("@alice:example.com", {
"VAX": {
user_id: "@alice:example.com",
device_id: "VAX",
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
keys: {
"ed25519:VAX": vaxDevice.deviceEd25519Key,
"curve25519:VAX": vaxDevice.deviceCurve25519Key,
},
},
});
vax.client._crypto._deviceList.storeDevicesForUser("@alice:example.com", {
"Osborne2": {
user_id: "@alice:example.com",
device_id: "Osborne2",
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
keys: {
"ed25519:Osborne2": osborne2Device.deviceEd25519Key,
"curve25519:Osborne2": osborne2Device.deviceCurve25519Key,
},
},
});
await osborne2Device.generateOneTimeKeys(1);
const otks = (await osborne2Device.getOneTimeKeys()).curve25519;
await osborne2Device.markKeysAsPublished();
await vax.client._crypto._olmDevice.createOutboundSession(
osborne2Device.deviceCurve25519Key,
Object.values(otks)[0],
);
const request = await secretStorage.request("foo", ["VAX"]);
const secret = await request.promise;
expect(secret).toBe("bar");
});
describe("bootstrap", function() {
// keys used in some of the tests
const XSK = new Uint8Array(
olmlib.decodeBase64("3lo2YdJugHjfE+Or7KJ47NuKbhE7AAGLgQ/dc19913Q="),
);
const XSPubKey = "DRb8pFVJyEJ9OWvXeUoM0jq/C2Wt+NxzBZVuk2nRb+0";
const USK = new Uint8Array(
olmlib.decodeBase64("lKWi3hJGUie5xxHgySoz8PHFnZv6wvNaud/p2shN9VU="),
);
const USPubKey = "CUpoiTtHiyXpUmd+3ohb7JVxAlUaOG1NYs9Jlx8soQU";
const SSK = new Uint8Array(
olmlib.decodeBase64("1R6JVlXX99UcfUZzKuCDGQgJTw8ur1/ofgPD8pp+96M="),
);
const SSPubKey = "0DfNsRDzEvkCLA0gD3m7VAGJ5VClhjEsewI35xq873Q";
const SSSSKey = new Uint8Array(
olmlib.decodeBase64(
"XrmITOOdBhw6yY5Bh7trb/bgp1FRdIGyCUxxMP873R0=",
),
);
it("bootstraps when no storage or cross-signing keys locally", async function() {
const key = new Uint8Array(16);
for (let i = 0; i < 16; i++) key[i] = i;
const getKey = jest.fn(e => {
return [Object.keys(e.keys)[0], key];
});
const bob = await makeTestClient(
{
userId: "@bob:example.com",
deviceId: "bob1",
},
{
cryptoCallbacks: {
getSecretStorageKey: getKey,
},
},
);
bob.uploadDeviceSigningKeys = async () => {};
bob.uploadKeySignatures = async () => {};
bob.setAccountData = async function(eventType, contents, callback) {
const event = new MatrixEvent({
type: eventType,
content: contents,
});
this.store.storeAccountDataEvents([
event,
]);
this.emit("accountData", event);
};
await bob.bootstrapSecretStorage();
const crossSigning = bob._crypto._crossSigningInfo;
const secretStorage = bob._crypto._secretStorage;
expect(crossSigning.getId()).toBeTruthy();
expect(await crossSigning.isStoredInSecretStorage(secretStorage))
.toBeTruthy();
expect(await secretStorage.hasKey()).toBeTruthy();
});
it("bootstraps when cross-signing keys in secret storage", async function() {
const decryption = new global.Olm.PkDecryption();
const storagePublicKey = decryption.generate_key();
const storagePrivateKey = decryption.get_private_key();
const bob = await makeTestClient(
{
userId: "@bob:example.com",
deviceId: "bob1",
},
{
cryptoCallbacks: {
getSecretStorageKey: async request => {
const defaultKeyId = await bob.getDefaultSecretStorageKeyId();
expect(Object.keys(request.keys)).toEqual([defaultKeyId]);
return [defaultKeyId, storagePrivateKey];
},
},
},
);
bob.uploadDeviceSigningKeys = async () => {};
bob.uploadKeySignatures = async () => {};
bob.setAccountData = async function(eventType, contents, callback) {
const event = new MatrixEvent({
type: eventType,
content: contents,
});
this.store.storeAccountDataEvents([
event,
]);
this.emit("accountData", event);
};
bob._crypto.checkKeyBackup = async () => {};
const crossSigning = bob._crypto._crossSigningInfo;
const secretStorage = bob._crypto._secretStorage;
// Set up cross-signing keys from scratch with specific storage key
await bob.bootstrapSecretStorage({
createSecretStorageKey: async () => ({
// `pubkey` not used anymore with symmetric 4S
keyInfo: { pubkey: storagePublicKey },
privateKey: storagePrivateKey,
}),
});
// Clear local cross-signing keys and read from secret storage
bob._crypto._deviceList.storeCrossSigningForUser(
"@bob:example.com",
crossSigning.toStorage(),
);
crossSigning.keys = {};
await bob.bootstrapSecretStorage();
expect(crossSigning.getId()).toBeTruthy();
expect(await crossSigning.isStoredInSecretStorage(secretStorage))
.toBeTruthy();
expect(await secretStorage.hasKey()).toBeTruthy();
});
it("adds passphrase checking if it's lacking", async function() {
let crossSigningKeys = {
master: XSK,
user_signing: USK,
self_signing: SSK,
};
const secretStorageKeys = {
key_id: SSSSKey,
};
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
{
cryptoCallbacks: {
getCrossSigningKey: t => crossSigningKeys[t],
saveCrossSigningKeys: k => crossSigningKeys = k,
getSecretStorageKey: ({keys}, name) => {
for (const keyId of Object.keys(keys)) {
if (secretStorageKeys[keyId]) {
return [keyId, secretStorageKeys[keyId]];
}
}
},
},
},
);
alice.store.storeAccountDataEvents([
new MatrixEvent({
type: "m.secret_storage.default_key",
content: {
key: "key_id",
},
}),
new MatrixEvent({
type: "m.secret_storage.key.key_id",
content: {
algorithm: "m.secret_storage.v1.aes-hmac-sha2",
passphrase: {
algorithm: "m.pbkdf2",
iterations: 500000,
salt: "GbkvwKHVMveo1zGVSb2GMMdCinG2npJK",
},
},
}),
// we never use these values, other than checking that they
// exist, so just use dummy values
new MatrixEvent({
type: "m.cross_signing.master",
content: {
encrypted: {
key_id: {ciphertext: "bla", mac: "bla", iv: "bla"},
},
},
}),
new MatrixEvent({
type: "m.cross_signing.self_signing",
content: {
encrypted: {
key_id: {ciphertext: "bla", mac: "bla", iv: "bla"},
},
},
}),
new MatrixEvent({
type: "m.cross_signing.user_signing",
content: {
encrypted: {
key_id: {ciphertext: "bla", mac: "bla", iv: "bla"},
},
},
}),
]);
alice._crypto._deviceList.storeCrossSigningForUser("@alice:example.com", {
keys: {
master: {
user_id: "@alice:example.com",
usage: ["master"],
keys: {
[`ed25519:${XSPubKey}`]: XSPubKey,
},
},
self_signing: sign({
user_id: "@alice:example.com",
usage: ["self_signing"],
keys: {
[`ed25519:${SSPubKey}`]: SSPubKey,
},
}, XSK, "@alice:example.com"),
user_signing: sign({
user_id: "@alice:example.com",
usage: ["user_signing"],
keys: {
[`ed25519:${USPubKey}`]: USPubKey,
},
}, XSK, "@alice:example.com"),
},
});
alice.getKeyBackupVersion = async () => {
return {
version: "1",
algorithm: "m.megolm_backup.v1.curve25519-aes-sha2",
auth_data: sign({
public_key: "pxEXhg+4vdMf/kFwP4bVawFWdb0EmytL3eFJx++zQ0A",
}, XSK, "@alice:example.com"),
};
};
alice.setAccountData = async function(name, data) {
const event = new MatrixEvent({
type: name,
content: data,
});
alice.store.storeAccountDataEvents([event]);
this.emit("accountData", event);
};
await alice.bootstrapSecretStorage();
expect(alice.getAccountData("m.secret_storage.default_key").getContent())
.toEqual({key: "key_id"});
const keyInfo = alice.getAccountData("m.secret_storage.key.key_id")
.getContent();
expect(keyInfo.algorithm)
.toEqual("m.secret_storage.v1.aes-hmac-sha2");
expect(keyInfo.passphrase).toEqual({
algorithm: "m.pbkdf2",
iterations: 500000,
salt: "GbkvwKHVMveo1zGVSb2GMMdCinG2npJK",
});
expect(keyInfo).toHaveProperty("iv");
expect(keyInfo).toHaveProperty("mac");
expect(alice.checkSecretStorageKey(secretStorageKeys.key_id, keyInfo))
.toBeTruthy();
});
it("fixes backup keys in the wrong format", async function() {
let crossSigningKeys = {
master: XSK,
user_signing: USK,
self_signing: SSK,
};
const secretStorageKeys = {
key_id: SSSSKey,
};
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
{
cryptoCallbacks: {
getCrossSigningKey: t => crossSigningKeys[t],
saveCrossSigningKeys: k => crossSigningKeys = k,
getSecretStorageKey: ({keys}, name) => {
for (const keyId of Object.keys(keys)) {
if (secretStorageKeys[keyId]) {
return [keyId, secretStorageKeys[keyId]];
}
}
},
},
},
);
alice.store.storeAccountDataEvents([
new MatrixEvent({
type: "m.secret_storage.default_key",
content: {
key: "key_id",
},
}),
new MatrixEvent({
type: "m.secret_storage.key.key_id",
content: {
algorithm: "m.secret_storage.v1.aes-hmac-sha2",
passphrase: {
algorithm: "m.pbkdf2",
iterations: 500000,
salt: "GbkvwKHVMveo1zGVSb2GMMdCinG2npJK",
},
},
}),
new MatrixEvent({
type: "m.cross_signing.master",
content: {
encrypted: {
key_id: {ciphertext: "bla", mac: "bla", iv: "bla"},
},
},
}),
new MatrixEvent({
type: "m.cross_signing.self_signing",
content: {
encrypted: {
key_id: {ciphertext: "bla", mac: "bla", iv: "bla"},
},
},
}),
new MatrixEvent({
type: "m.cross_signing.user_signing",
content: {
encrypted: {
key_id: {ciphertext: "bla", mac: "bla", iv: "bla"},
},
},
}),
new MatrixEvent({
type: "m.megolm_backup.v1",
content: {
encrypted: {
key_id: await encryptAES(
"123,45,6,7,89,1,234,56,78,90,12,34,5,67,8,90",
secretStorageKeys.key_id, "m.megolm_backup.v1",
),
},
},
}),
]);
alice._crypto._deviceList.storeCrossSigningForUser("@alice:example.com", {
keys: {
master: {
user_id: "@alice:example.com",
usage: ["master"],
keys: {
[`ed25519:${XSPubKey}`]: XSPubKey,
},
},
self_signing: sign({
user_id: "@alice:example.com",
usage: ["self_signing"],
keys: {
[`ed25519:${SSPubKey}`]: SSPubKey,
},
}, XSK, "@alice:example.com"),
user_signing: sign({
user_id: "@alice:example.com",
usage: ["user_signing"],
keys: {
[`ed25519:${USPubKey}`]: USPubKey,
},
}, XSK, "@alice:example.com"),
},
});
alice.getKeyBackupVersion = async () => {
return {
version: "1",
algorithm: "m.megolm_backup.v1.curve25519-aes-sha2",
auth_data: sign({
public_key: "pxEXhg+4vdMf/kFwP4bVawFWdb0EmytL3eFJx++zQ0A",
}, XSK, "@alice:example.com"),
};
};
alice.setAccountData = async function(name, data) {
const event = new MatrixEvent({
type: name,
content: data,
});
alice.store.storeAccountDataEvents([event]);
this.emit("accountData", event);
};
await alice.bootstrapSecretStorage();
const backupKey = alice.getAccountData("m.megolm_backup.v1")
.getContent();
expect(backupKey.encrypted).toHaveProperty("key_id");
expect(await alice.getSecret("m.megolm_backup.v1"))
.toEqual("ey0GB1kB6jhOWgwiBUMIWg==");
});
});
});
@@ -0,0 +1,98 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
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 {InRoomChannel} from "../../../../src/crypto/verification/request/InRoomChannel";
"../../../../src/crypto/verification/request/ToDeviceChannel";
import {MatrixEvent} from "../../../../src/models/event";
describe("InRoomChannel tests", function() {
const ALICE = "@alice:hs.tld";
const BOB = "@bob:hs.tld";
const MALORY = "@malory:hs.tld";
const client = {
getUserId() { return ALICE; },
};
it("getEventType only returns .request for a message with a msgtype", function() {
const invalidEvent = new MatrixEvent({
type: "m.key.verification.request",
});
expect(InRoomChannel.getEventType(invalidEvent)).toStrictEqual("");
const validEvent = new MatrixEvent({
type: "m.room.message",
content: { msgtype: "m.key.verification.request" },
});
expect(InRoomChannel.getEventType(validEvent)).
toStrictEqual("m.key.verification.request");
const validFooEvent = new MatrixEvent({ type: "m.foo" });
expect(InRoomChannel.getEventType(validFooEvent)).
toStrictEqual("m.foo");
});
it("getEventType should return m.room.message for messages", function() {
const messageEvent = new MatrixEvent({
type: "m.room.message",
content: { msgtype: "m.text" },
});
// XXX: The event type doesn't matter too much, just as long as it's not a verification event
expect(InRoomChannel.getEventType(messageEvent)).
toStrictEqual("m.room.message");
});
it("getEventType should return actual type for non-message events", function() {
const event = new MatrixEvent({
type: "m.room.member",
content: { },
});
expect(InRoomChannel.getEventType(event)).
toStrictEqual("m.room.member");
});
it("getOtherPartyUserId should not return anything for a request not " +
"directed at me", function() {
const event = new MatrixEvent({
sender: BOB,
type: "m.room.message",
content: { msgtype: "m.key.verification.request", to: MALORY },
});
expect(InRoomChannel.getOtherPartyUserId(event, client)).toStrictEqual(undefined);
});
it("getOtherPartyUserId should not return anything an event that is not of a valid " +
"request type", function() {
// invalid because this should be a room message with msgtype
const invalidRequest = new MatrixEvent({
sender: BOB,
type: "m.key.verification.request",
content: { to: ALICE },
});
expect(InRoomChannel.getOtherPartyUserId(invalidRequest, client))
.toStrictEqual(undefined);
const startEvent = new MatrixEvent({
sender: BOB,
type: "m.key.verification.start",
content: { to: ALICE },
});
expect(InRoomChannel.getOtherPartyUserId(startEvent, client))
.toStrictEqual(undefined);
const fooEvent = new MatrixEvent({
sender: BOB,
type: "m.foo",
content: { to: ALICE },
});
expect(InRoomChannel.getOtherPartyUserId(fooEvent, client))
.toStrictEqual(undefined);
});
});
@@ -0,0 +1,41 @@
/*
Copyright 2018-2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
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 "../../../olm-loader";
import {logger} from "../../../../src/logger";
const Olm = global.Olm;
describe("QR code verification", function() {
if (!global.Olm) {
logger.warn('Not running device verification tests: libolm not present');
return;
}
beforeAll(function() {
return Olm.init();
});
describe("reciprocate", () => {
it("should verify the secret", () => {
// TODO: Actually write a test for this.
// Tests are hard because we are running before the verification
// process actually begins, and are largely UI-driven rather than
// logic-driven (compared to something like SAS). In the interest
// of time, tests are currently excluded.
});
});
});
@@ -0,0 +1,82 @@
/*
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.
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 "../../../olm-loader";
import {verificationMethods} from "../../../../src/crypto";
import {logger} from "../../../../src/logger";
import {SAS} from "../../../../src/crypto/verification/SAS";
import {makeTestClients, setupWebcrypto, teardownWebcrypto} from './util';
const Olm = global.Olm;
jest.useFakeTimers();
describe("verification request integration tests with crypto layer", function() {
if (!global.Olm) {
logger.warn('Not running device verification unit tests: libolm not present');
return;
}
beforeAll(function() {
setupWebcrypto();
return Olm.init();
});
afterAll(() => {
teardownWebcrypto();
});
it("should request and accept a verification", async function() {
const [alice, bob] = await makeTestClients(
[
{userId: "@alice:example.com", deviceId: "Osborne2"},
{userId: "@bob:example.com", deviceId: "Dynabook"},
],
{
verificationMethods: [verificationMethods.SAS],
},
);
alice.client._crypto._deviceList.getRawStoredDevicesForUser = function() {
return {
Dynabook: {
keys: {
"ed25519:Dynabook": "bob+base64+ed25519+key",
},
},
};
};
alice.client.downloadKeys = () => {
return Promise.resolve();
};
bob.client.downloadKeys = () => {
return Promise.resolve();
};
bob.client.on("crypto.verification.request", (request) => {
const bobVerifier = request.beginKeyVerification(verificationMethods.SAS);
bobVerifier.verify();
// XXX: Private function access (but it's a test, so we're okay)
bobVerifier._endTimer();
});
const aliceRequest = await alice.client.requestVerification("@bob:example.com");
await aliceRequest.waitFor(r => r.started);
const aliceVerifier = aliceRequest.verifier;
expect(aliceVerifier).toBeInstanceOf(SAS);
// XXX: Private function access (but it's a test, so we're okay)
aliceVerifier._endTimer();
});
});
+505
View File
@@ -0,0 +1,505 @@
/*
Copyright 2018-2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
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 "../../../olm-loader";
import {makeTestClients, setupWebcrypto, teardownWebcrypto} from './util';
import {MatrixEvent} from "../../../../src/models/event";
import {SAS} from "../../../../src/crypto/verification/SAS";
import {DeviceInfo} from "../../../../src/crypto/deviceinfo";
import {verificationMethods} from "../../../../src/crypto";
import * as olmlib from "../../../../src/crypto/olmlib";
import {logger} from "../../../../src/logger";
const Olm = global.Olm;
let ALICE_DEVICES;
let BOB_DEVICES;
describe("SAS verification", function() {
if (!global.Olm) {
logger.warn('Not running device verification unit tests: libolm not present');
return;
}
beforeAll(function() {
setupWebcrypto();
return Olm.init();
});
afterAll(() => {
teardownWebcrypto();
});
it("should error on an unexpected event", async function() {
//channel, baseApis, userId, deviceId, startEvent, request
const request = {
onVerifierCancelled: function() {},
};
const channel = {
send: function() {
return Promise.resolve();
},
};
const sas = new SAS(channel, {}, "@alice:example.com", "ABCDEFG", null, request);
sas.handleEvent(new MatrixEvent({
sender: "@alice:example.com",
type: "es.inquisition",
content: {},
}));
const spy = jest.fn();
await sas.verify().catch(spy);
expect(spy).toHaveBeenCalled();
// Cancel the SAS for cleanup (we started a verification, so abort)
sas.cancel();
});
describe("verification", () => {
let alice;
let bob;
let aliceSasEvent;
let bobSasEvent;
let aliceVerifier;
let bobPromise;
beforeEach(async () => {
[alice, bob] = await makeTestClients(
[
{userId: "@alice:example.com", deviceId: "Osborne2"},
{userId: "@bob:example.com", deviceId: "Dynabook"},
],
{
verificationMethods: [verificationMethods.SAS],
},
);
const aliceDevice = alice.client._crypto._olmDevice;
const bobDevice = bob.client._crypto._olmDevice;
ALICE_DEVICES = {
Osborne2: {
user_id: "@alice:example.com",
device_id: "Osborne2",
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
keys: {
"ed25519:Osborne2": aliceDevice.deviceEd25519Key,
"curve25519:Osborne2": aliceDevice.deviceCurve25519Key,
},
},
};
BOB_DEVICES = {
Dynabook: {
user_id: "@bob:example.com",
device_id: "Dynabook",
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
keys: {
"ed25519:Dynabook": bobDevice.deviceEd25519Key,
"curve25519:Dynabook": bobDevice.deviceCurve25519Key,
},
},
};
alice.client._crypto._deviceList.storeDevicesForUser(
"@bob:example.com", BOB_DEVICES,
);
alice.client.downloadKeys = () => {
return Promise.resolve();
};
bob.client._crypto._deviceList.storeDevicesForUser(
"@alice:example.com", ALICE_DEVICES,
);
bob.client.downloadKeys = () => {
return Promise.resolve();
};
aliceSasEvent = null;
bobSasEvent = null;
bobPromise = new Promise((resolve, reject) => {
bob.client.on("crypto.verification.request", request => {
request.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();
}
}
});
resolve(request.verifier);
});
});
aliceVerifier = alice.client.beginKeyVerification(
verificationMethods.SAS, bob.client.getUserId(), bob.deviceId,
);
aliceVerifier.on("show_sas", (e) => {
if (!e.sas.emoji || !e.sas.decimal) {
e.cancel();
} else if (!bobSasEvent) {
aliceSasEvent = e;
} else {
try {
expect(e.sas).toEqual(bobSasEvent.sas);
e.confirm();
bobSasEvent.confirm();
} catch (error) {
e.mismatch();
bobSasEvent.mismatch();
}
}
});
});
afterEach(async () => {
await Promise.all([
alice.stop(),
bob.stop(),
]);
});
it("should verify a key", async () => {
let macMethod;
let keyAgreement;
const origSendToDevice = bob.client.sendToDevice.bind(bob.client);
bob.client.sendToDevice = function(type, map) {
if (type === "m.key.verification.accept") {
macMethod = map[alice.client.getUserId()][alice.client.deviceId]
.message_authentication_code;
keyAgreement = map[alice.client.getUserId()][alice.client.deviceId]
.key_agreement_protocol;
}
return origSendToDevice(type, map);
};
alice.httpBackend.when('POST', '/keys/query').respond(200, {
failures: {},
device_keys: {
"@bob:example.com": BOB_DEVICES,
},
});
bob.httpBackend.when('POST', '/keys/query').respond(200, {
failures: {},
device_keys: {
"@alice:example.com": ALICE_DEVICES,
},
});
await Promise.all([
aliceVerifier.verify(),
bobPromise.then((verifier) => verifier.verify()),
alice.httpBackend.flush(),
bob.httpBackend.flush(),
]);
// make sure that it uses the preferred method
expect(macMethod).toBe("hkdf-hmac-sha256");
expect(keyAgreement).toBe("curve25519-hkdf-sha256");
// make sure Alice and Bob verified each other
const bobDevice
= await alice.client.getStoredDevice("@bob:example.com", "Dynabook");
expect(bobDevice.isVerified()).toBeTruthy();
const aliceDevice
= await bob.client.getStoredDevice("@alice:example.com", "Osborne2");
expect(aliceDevice.isVerified()).toBeTruthy();
});
it("should be able to verify using the old MAC", async () => {
// pretend that Alice can only understand the old (incorrect) MAC,
// and make sure that she can still verify with Bob
let macMethod;
const aliceOrigSendToDevice = alice.client.sendToDevice.bind(alice.client);
alice.client.sendToDevice = (type, map) => {
if (type === "m.key.verification.start") {
// Note: this modifies not only the message that Bob
// receives, but also the copy of the message that Alice
// has, since it is the same object. If this does not
// happen, the verification will fail due to a hash
// commitment mismatch.
map[bob.client.getUserId()][bob.client.deviceId]
.message_authentication_codes = ['hmac-sha256'];
}
return aliceOrigSendToDevice(type, map);
};
const bobOrigSendToDevice = bob.client.sendToDevice.bind(bob.client);
bob.client.sendToDevice = (type, map) => {
if (type === "m.key.verification.accept") {
macMethod = map[alice.client.getUserId()][alice.client.deviceId]
.message_authentication_code;
}
return bobOrigSendToDevice(type, map);
};
alice.httpBackend.when('POST', '/keys/query').respond(200, {
failures: {},
device_keys: {
"@bob:example.com": BOB_DEVICES,
},
});
bob.httpBackend.when('POST', '/keys/query').respond(200, {
failures: {},
device_keys: {
"@alice:example.com": ALICE_DEVICES,
},
});
await Promise.all([
aliceVerifier.verify(),
bobPromise.then((verifier) => verifier.verify()),
alice.httpBackend.flush(),
bob.httpBackend.flush(),
]);
expect(macMethod).toBe("hmac-sha256");
const bobDevice
= await alice.client.getStoredDevice("@bob:example.com", "Dynabook");
expect(bobDevice.isVerified()).toBeTruthy();
const aliceDevice
= await bob.client.getStoredDevice("@alice:example.com", "Osborne2");
expect(aliceDevice.isVerified()).toBeTruthy();
});
it("should verify a cross-signing key", async () => {
alice.httpBackend.when('POST', '/keys/device_signing/upload').respond(
200, {},
);
alice.httpBackend.when('POST', '/keys/signatures/upload').respond(200, {});
alice.httpBackend.flush(undefined, 2);
await alice.client.resetCrossSigningKeys();
bob.httpBackend.when('POST', '/keys/device_signing/upload').respond(200, {});
bob.httpBackend.when('POST', '/keys/signatures/upload').respond(200, {});
bob.httpBackend.flush(undefined, 2);
await bob.client.resetCrossSigningKeys();
bob.client._crypto._deviceList.storeCrossSigningForUser(
"@alice:example.com", {
keys: alice.client._crypto._crossSigningInfo.keys,
},
);
const verifyProm = Promise.all([
aliceVerifier.verify(),
bobPromise.then((verifier) => {
bob.httpBackend.when(
'POST', '/keys/signatures/upload',
).respond(200, {});
bob.httpBackend.flush(undefined, 1, 2000);
return verifier.verify();
}),
]);
await verifyProm;
const bobDeviceTrust = alice.client.checkDeviceTrust(
"@bob:example.com", "Dynabook",
);
expect(bobDeviceTrust.isLocallyVerified()).toBeTruthy();
expect(bobDeviceTrust.isCrossSigningVerified()).toBeFalsy();
const aliceTrust = bob.client.checkUserTrust("@alice:example.com");
expect(aliceTrust.isCrossSigningVerified()).toBeTruthy();
expect(aliceTrust.isTofu()).toBeTruthy();
const aliceDeviceTrust = bob.client.checkDeviceTrust(
"@alice:example.com", "Osborne2",
);
expect(aliceDeviceTrust.isLocallyVerified()).toBeTruthy();
expect(aliceDeviceTrust.isCrossSigningVerified()).toBeFalsy();
});
});
it("should send a cancellation message on error", async function() {
const [alice, bob] = await makeTestClients(
[
{userId: "@alice:example.com", deviceId: "Osborne2"},
{userId: "@bob:example.com", deviceId: "Dynabook"},
],
{
verificationMethods: [verificationMethods.SAS],
},
);
alice.client.setDeviceVerified = jest.fn();
alice.client.downloadKeys = () => {
return Promise.resolve();
};
bob.client.setDeviceVerified = jest.fn();
bob.client.downloadKeys = () => {
return Promise.resolve();
};
const bobPromise = new Promise((resolve, reject) => {
bob.client.on("crypto.verification.request", request => {
request.verifier.on("show_sas", (e) => {
e.mismatch();
});
resolve(request.verifier);
});
});
const aliceVerifier = alice.client.beginKeyVerification(
verificationMethods.SAS, bob.client.getUserId(), bob.client.deviceId,
);
const aliceSpy = jest.fn();
const bobSpy = jest.fn();
await Promise.all([
aliceVerifier.verify().catch(aliceSpy),
bobPromise.then((verifier) => verifier.verify()).catch(bobSpy),
]);
expect(aliceSpy).toHaveBeenCalled();
expect(bobSpy).toHaveBeenCalled();
expect(alice.client.setDeviceVerified)
.not.toHaveBeenCalled();
expect(bob.client.setDeviceVerified)
.not.toHaveBeenCalled();
});
describe("verification in DM", function() {
let alice;
let bob;
let aliceSasEvent;
let bobSasEvent;
let aliceVerifier;
let bobPromise;
beforeEach(async function() {
[alice, bob] = await makeTestClients(
[
{userId: "@alice:example.com", deviceId: "Osborne2"},
{userId: "@bob:example.com", deviceId: "Dynabook"},
],
{
verificationMethods: [verificationMethods.SAS],
},
);
alice.client.setDeviceVerified = jest.fn();
alice.client.getDeviceEd25519Key = () => {
return "alice+base64+ed25519+key";
};
alice.client.getStoredDevice = () => {
return DeviceInfo.fromStorage(
{
keys: {
"ed25519:Dynabook": "bob+base64+ed25519+key",
},
},
"Dynabook",
);
};
alice.client.downloadKeys = () => {
return Promise.resolve();
};
bob.client.setDeviceVerified = jest.fn();
bob.client.getStoredDevice = () => {
return DeviceInfo.fromStorage(
{
keys: {
"ed25519:Osborne2": "alice+base64+ed25519+key",
},
},
"Osborne2",
);
};
bob.client.getDeviceEd25519Key = () => {
return "bob+base64+ed25519+key";
};
bob.client.downloadKeys = () => {
return Promise.resolve();
};
aliceSasEvent = null;
bobSasEvent = null;
bobPromise = new Promise((resolve, reject) => {
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();
});
});
const aliceRequest = await alice.client.requestVerificationDM(
bob.client.getUserId(), "!room_id",
);
await aliceRequest.waitFor(r => r.started);
aliceVerifier = aliceRequest.verifier;
aliceVerifier.on("show_sas", (e) => {
if (!e.sas.emoji || !e.sas.decimal) {
e.cancel();
} else if (!bobSasEvent) {
aliceSasEvent = e;
} else {
try {
expect(e.sas).toEqual(bobSasEvent.sas);
e.confirm();
bobSasEvent.confirm();
} catch (error) {
e.mismatch();
bobSasEvent.mismatch();
}
}
});
});
afterEach(async function() {
await Promise.all([
alice.stop(),
bob.stop(),
]);
});
it("should verify a key", async function() {
await Promise.all([
aliceVerifier.verify(),
bobPromise,
]);
// make sure Alice and Bob verified each other
expect(alice.client.setDeviceVerified)
.toHaveBeenCalledWith(bob.client.getUserId(), bob.client.deviceId);
expect(bob.client.setDeviceVerified)
.toHaveBeenCalledWith(alice.client.getUserId(), alice.client.deviceId);
});
});
});
@@ -0,0 +1,117 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
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 {VerificationBase} from '../../../../src/crypto/verification/Base';
import {CrossSigningInfo} from '../../../../src/crypto/CrossSigning';
import {encodeBase64} from "../../../../src/crypto/olmlib";
import {setupWebcrypto, teardownWebcrypto} from './util';
jest.useFakeTimers();
// Private key for tests only
const testKey = new Uint8Array([
0xda, 0x5a, 0x27, 0x60, 0xe3, 0x3a, 0xc5, 0x82,
0x9d, 0x12, 0xc3, 0xbe, 0xe8, 0xaa, 0xc2, 0xef,
0xae, 0xb1, 0x05, 0xc1, 0xe7, 0x62, 0x78, 0xa6,
0xd7, 0x1f, 0xf8, 0x2c, 0x51, 0x85, 0xf0, 0x1d,
]);
const testKeyPub = "nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk";
describe("self-verifications", () => {
beforeAll(function() {
setupWebcrypto();
return global.Olm.init();
});
afterAll(() => {
teardownWebcrypto();
});
it("triggers a request for key sharing upon completion", async () => {
const userId = "@test:localhost";
const cacheCallbacks = {
getCrossSigningKeyCache: jest.fn().mockReturnValue(null),
storeCrossSigningKeyCache: jest.fn(),
};
const _crossSigningInfo = new CrossSigningInfo(
userId,
{},
cacheCallbacks,
);
_crossSigningInfo.keys = {
self_signing: { keys: { X: testKeyPub } },
user_signing: { keys: { X: testKeyPub } },
};
const _secretStorage = {
request: jest.fn().mockReturnValue({
promise: Promise.resolve(encodeBase64(testKey)),
}),
};
const storeSessionBackupPrivateKey = jest.fn();
const restoreKeyBackupWithCache = jest.fn(() => Promise.resolve());
const client = {
_crypto: {
_crossSigningInfo,
_secretStorage,
storeSessionBackupPrivateKey,
getSessionBackupPrivateKey: () => null,
},
requestSecret: _secretStorage.request.bind(_secretStorage),
getUserId: () => userId,
getKeyBackupVersion: () => Promise.resolve({}),
restoreKeyBackupWithCache,
};
const request = {
onVerifierFinished: () => undefined,
};
const verification = new VerificationBase(
undefined, // channel
client, // baseApis
userId,
"ABC", // deviceId
undefined, // startEvent
request,
);
verification._resolve = () => undefined;
const result = await verification.done();
/* We should request, and store, two cross signing key and the key backup key */
expect(cacheCallbacks.storeCrossSigningKeyCache.mock.calls.length).toBe(2);
expect(_secretStorage.request.mock.calls.length).toBe(3);
expect(cacheCallbacks.storeCrossSigningKeyCache.mock.calls[0][1])
.toEqual(testKey);
expect(cacheCallbacks.storeCrossSigningKeyCache.mock.calls[1][1])
.toEqual(testKey);
expect(storeSessionBackupPrivateKey.mock.calls[0][0])
.toEqual(testKey);
expect(restoreKeyBackupWithCache).toHaveBeenCalled();
expect(result).toBeInstanceOf(Array);
expect(result[0][0]).toBe(testKeyPub);
expect(result[1][0]).toBe(testKeyPub);
});
});
+117
View File
@@ -0,0 +1,117 @@
/*
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.
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 {TestClient} from '../../../TestClient';
import {MatrixEvent} from "../../../../src/models/event";
import nodeCrypto from "crypto";
export async function makeTestClients(userInfos, options) {
const clients = [];
const clientMap = {};
const sendToDevice = function(type, map) {
// console.log(this.getUserId(), "sends", type, map);
for (const [userId, devMap] of Object.entries(map)) {
if (userId in clientMap) {
for (const [deviceId, msg] of Object.entries(devMap)) {
if (deviceId in clientMap[userId]) {
const event = new MatrixEvent({
sender: this.getUserId(), // eslint-disable-line babel/no-invalid-this
type: type,
content: msg,
});
const client = clientMap[userId][deviceId];
const decryptionPromise = event.isEncrypted() ?
event.attemptDecryption(client._crypto) :
Promise.resolve();
decryptionPromise.then(
() => client.emit("toDeviceEvent", event),
);
}
}
}
}
};
const sendEvent = function(room, type, content) {
// make up a unique ID as the event ID
const eventId = "$" + this.makeTxnId(); // eslint-disable-line babel/no-invalid-this
const rawEvent = {
sender: this.getUserId(), // eslint-disable-line babel/no-invalid-this
type: type,
content: content,
room_id: room,
event_id: eventId,
origin_server_ts: Date.now(),
};
const event = new MatrixEvent(rawEvent);
const remoteEcho = new MatrixEvent(Object.assign({}, rawEvent, {
unsigned: {
transaction_id: this.makeTxnId(), // eslint-disable-line babel/no-invalid-this
},
}));
setImmediate(() => {
for (const tc of clients) {
if (tc.client === this) { // eslint-disable-line babel/no-invalid-this
console.log("sending remote echo!!");
tc.client.emit("Room.timeline", remoteEcho);
} else {
tc.client.emit("Room.timeline", event);
}
}
});
return Promise.resolve({event_id: eventId});
};
for (const userInfo of userInfos) {
let keys = {};
if (!options) options = {};
if (!options.cryptoCallbacks) options.cryptoCallbacks = {};
if (!options.cryptoCallbacks.saveCrossSigningKeys) {
options.cryptoCallbacks.saveCrossSigningKeys = k => { keys = k; };
options.cryptoCallbacks.getCrossSigningKey = typ => keys[typ];
}
const testClient = new TestClient(
userInfo.userId, userInfo.deviceId, undefined, undefined,
options,
);
if (!(userInfo.userId in clientMap)) {
clientMap[userInfo.userId] = {};
}
clientMap[userInfo.userId][userInfo.deviceId] = testClient.client;
testClient.client.sendToDevice = sendToDevice;
testClient.client.sendEvent = sendEvent;
clients.push(testClient);
}
await Promise.all(clients.map((testClient) => testClient.client.initCrypto()));
return clients;
}
export function setupWebcrypto() {
global.crypto = {
getRandomValues: (buf) => {
return nodeCrypto.randomFillSync(buf);
},
};
}
export function teardownWebcrypto() {
global.crypto = undefined;
}
@@ -0,0 +1,249 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
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, READY_TYPE, START_TYPE, DONE_TYPE} from
"../../../../src/crypto/verification/request/VerificationRequest";
import {InRoomChannel} from "../../../../src/crypto/verification/request/InRoomChannel";
import {ToDeviceChannel} from
"../../../../src/crypto/verification/request/ToDeviceChannel";
import {MatrixEvent} from "../../../../src/models/event";
import {setupWebcrypto, teardownWebcrypto} from "./util";
function makeMockClient(userId, deviceId) {
let counter = 1;
let events = [];
const deviceEvents = {};
return {
getUserId() { return userId; },
getDeviceId() { return deviceId; },
sendEvent(roomId, type, content) {
counter = counter + 1;
const eventId = `$${userId}-${deviceId}-${counter}`;
events.push(new MatrixEvent({
sender: userId,
event_id: eventId,
room_id: roomId,
type,
content,
origin_server_ts: Date.now(),
}));
return Promise.resolve({event_id: eventId});
},
sendToDevice(type, msgMap) {
for (const userId of Object.keys(msgMap)) {
const deviceMap = msgMap[userId];
for (const deviceId of Object.keys(deviceMap)) {
const content = deviceMap[deviceId];
const event = new MatrixEvent({content, type});
deviceEvents[userId] = deviceEvents[userId] || {};
deviceEvents[userId][deviceId] = deviceEvents[userId][deviceId] || [];
deviceEvents[userId][deviceId].push(event);
}
}
return Promise.resolve();
},
popEvents() {
const e = events;
events = [];
return e;
},
popDeviceEvents(userId, deviceId) {
const forDevice = deviceEvents[userId];
const events = forDevice && forDevice[deviceId];
const result = events || [];
if (events) {
delete forDevice[deviceId];
}
return result;
},
};
}
const MOCK_METHOD = "mock-verify";
class MockVerifier {
constructor(channel, client, userId, deviceId, startEvent) {
this._channel = channel;
this._startEvent = startEvent;
}
get events() {
return [DONE_TYPE];
}
async start() {
if (this._startEvent) {
await this._channel.send(DONE_TYPE, {});
} else {
await this._channel.send(START_TYPE, {method: MOCK_METHOD});
}
}
async handleEvent(event) {
if (event.getType() === DONE_TYPE && !this._startEvent) {
await this._channel.send(DONE_TYPE, {});
}
}
canSwitchStartEvent() {
return false;
}
}
function makeRemoteEcho(event) {
return new MatrixEvent(Object.assign({}, event.event, {
unsigned: {
transaction_id: "abc",
},
}));
}
async function distributeEvent(ownRequest, theirRequest, event) {
await ownRequest.channel.handleEvent(
makeRemoteEcho(event), ownRequest, true);
await theirRequest.channel.handleEvent(event, theirRequest, true);
}
describe("verification request unit tests", function() {
beforeAll(function() {
setupWebcrypto();
});
afterAll(() => {
teardownWebcrypto();
});
it("transition from UNSENT to DONE through happy path", async function() {
const alice = makeMockClient("@alice:matrix.tld", "device1");
const bob = makeMockClient("@bob:matrix.tld", "device1");
const aliceRequest = new VerificationRequest(
new InRoomChannel(alice, "!room", bob.getUserId()),
new Map([[MOCK_METHOD, MockVerifier]]), alice);
const bobRequest = new VerificationRequest(
new InRoomChannel(bob, "!room"),
new Map([[MOCK_METHOD, MockVerifier]]), bob);
expect(aliceRequest.invalid).toBe(true);
expect(bobRequest.invalid).toBe(true);
await aliceRequest.sendRequest();
const [requestEvent] = alice.popEvents();
expect(requestEvent.getType()).toBe("m.room.message");
await distributeEvent(aliceRequest, bobRequest, requestEvent);
expect(aliceRequest.requested).toBe(true);
expect(bobRequest.requested).toBe(true);
await bobRequest.accept();
const [readyEvent] = bob.popEvents();
expect(readyEvent.getType()).toBe(READY_TYPE);
await distributeEvent(bobRequest, aliceRequest, readyEvent);
expect(bobRequest.ready).toBe(true);
expect(aliceRequest.ready).toBe(true);
const verifier = aliceRequest.beginKeyVerification(MOCK_METHOD);
await verifier.start();
const [startEvent] = alice.popEvents();
expect(startEvent.getType()).toBe(START_TYPE);
await distributeEvent(aliceRequest, bobRequest, startEvent);
expect(aliceRequest.started).toBe(true);
expect(aliceRequest.verifier).toBeInstanceOf(MockVerifier);
expect(bobRequest.started).toBe(true);
expect(bobRequest.verifier).toBeInstanceOf(MockVerifier);
await bobRequest.verifier.start();
const [bobDoneEvent] = bob.popEvents();
expect(bobDoneEvent.getType()).toBe(DONE_TYPE);
await distributeEvent(bobRequest, aliceRequest, bobDoneEvent);
const [aliceDoneEvent] = alice.popEvents();
expect(aliceDoneEvent.getType()).toBe(DONE_TYPE);
await distributeEvent(aliceRequest, bobRequest, aliceDoneEvent);
expect(aliceRequest.done).toBe(true);
expect(bobRequest.done).toBe(true);
});
it("methods only contains common methods", async function() {
const alice = makeMockClient("@alice:matrix.tld", "device1");
const bob = makeMockClient("@bob:matrix.tld", "device1");
const aliceRequest = new VerificationRequest(
new InRoomChannel(alice, "!room", bob.getUserId()),
new Map([["c", function() {}], ["a", function() {}]]), alice);
const bobRequest = new VerificationRequest(
new InRoomChannel(bob, "!room"),
new Map([["c", function() {}], ["b", function() {}]]), bob);
await aliceRequest.sendRequest();
const [requestEvent] = alice.popEvents();
await distributeEvent(aliceRequest, bobRequest, requestEvent);
await bobRequest.accept();
const [readyEvent] = bob.popEvents();
await distributeEvent(bobRequest, aliceRequest, readyEvent);
expect(aliceRequest.methods).toStrictEqual(["c"]);
expect(bobRequest.methods).toStrictEqual(["c"]);
});
it("other client accepting request puts it in observeOnly mode", async function() {
const alice = makeMockClient("@alice:matrix.tld", "device1");
const bob1 = makeMockClient("@bob:matrix.tld", "device1");
const bob2 = makeMockClient("@bob:matrix.tld", "device2");
const aliceRequest = new VerificationRequest(
new InRoomChannel(alice, "!room", bob1.getUserId()), new Map(), alice);
await aliceRequest.sendRequest();
const [requestEvent] = alice.popEvents();
const bob1Request = new VerificationRequest(
new InRoomChannel(bob1, "!room"), new Map(), bob1);
const bob2Request = new VerificationRequest(
new InRoomChannel(bob2, "!room"), new Map(), bob2);
await bob1Request.channel.handleEvent(requestEvent, bob1Request, true);
await bob2Request.channel.handleEvent(requestEvent, bob2Request, true);
await bob1Request.accept();
const [readyEvent] = bob1.popEvents();
expect(bob2Request.observeOnly).toBe(false);
await bob2Request.channel.handleEvent(readyEvent, bob2Request, true);
expect(bob2Request.observeOnly).toBe(true);
});
it("verify own device with to_device messages", async function() {
const bob1 = makeMockClient("@bob:matrix.tld", "device1");
const bob2 = makeMockClient("@bob:matrix.tld", "device2");
const bob1Request = new VerificationRequest(
new ToDeviceChannel(bob1, bob1.getUserId(), ["device1", "device2"],
ToDeviceChannel.makeTransactionId(), "device2"),
new Map([[MOCK_METHOD, MockVerifier]]), bob1);
const to = {userId: "@bob:matrix.tld", deviceId: "device2"};
const verifier = bob1Request.beginKeyVerification(MOCK_METHOD, to);
expect(verifier).toBeInstanceOf(MockVerifier);
await verifier.start();
const [startEvent] = bob1.popDeviceEvents(to.userId, to.deviceId);
expect(startEvent.getType()).toBe(START_TYPE);
const bob2Request = new VerificationRequest(
new ToDeviceChannel(bob2, bob2.getUserId(), ["device1"]),
new Map([[MOCK_METHOD, MockVerifier]]), bob2);
await bob2Request.channel.handleEvent(startEvent, bob2Request, true);
await bob2Request.verifier.start();
const [doneEvent1] = bob2.popDeviceEvents("@bob:matrix.tld", "device1");
expect(doneEvent1.getType()).toBe(DONE_TYPE);
await bob1Request.channel.handleEvent(doneEvent1, bob1Request, true);
const [doneEvent2] = bob1.popDeviceEvents("@bob:matrix.tld", "device2");
expect(doneEvent2.getType()).toBe(DONE_TYPE);
await bob2Request.channel.handleEvent(doneEvent2, bob2Request, true);
expect(bob1Request.done).toBe(true);
expect(bob2Request.done).toBe(true);
});
});
+14 -20
View File
@@ -1,16 +1,12 @@
"use strict";
import 'source-map-support/register';
const sdk = require("../..");
const EventTimeline = sdk.EventTimeline;
const utils = require("../test-utils");
import * as utils from "../test-utils";
import {EventTimeline} from "../../src/models/event-timeline";
import {RoomState} from "../../src/models/room-state";
function mockRoomStates(timeline) {
timeline._startState = utils.mock(sdk.RoomState, "startState");
timeline._endState = utils.mock(sdk.RoomState, "endState");
timeline._startState = utils.mock(RoomState, "startState");
timeline._endState = utils.mock(RoomState, "endState");
}
import expect from 'expect';
describe("EventTimeline", function() {
const roomId = "!foo:bar";
const userA = "@alice:bar";
@@ -18,8 +14,6 @@ describe("EventTimeline", function() {
let timeline;
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this
// XXX: this is a horrid hack; should use sinon or something instead to mock
const timelineSet = { room: { roomId: roomId }};
timelineSet.room.getUnfilteredTimelineSet = function() {
@@ -78,7 +72,7 @@ describe("EventTimeline", function() {
expect(function() {
timeline.initialiseState(state);
}).toNotThrow();
}).not.toThrow();
timeline.addEvent(event, false);
expect(function() {
timeline.initialiseState(state);
@@ -121,7 +115,7 @@ describe("EventTimeline", function() {
const next = {b: "b"};
expect(function() {
timeline.setNeighbouringTimeline(prev, EventTimeline.BACKWARDS);
}).toNotThrow();
}).not.toThrow();
expect(timeline.getNeighbouringTimeline(EventTimeline.BACKWARDS))
.toBe(prev);
expect(function() {
@@ -130,7 +124,7 @@ describe("EventTimeline", function() {
expect(function() {
timeline.setNeighbouringTimeline(next, EventTimeline.FORWARDS);
}).toNotThrow();
}).not.toThrow();
expect(timeline.getNeighbouringTimeline(EventTimeline.FORWARDS))
.toBe(next);
expect(function() {
@@ -187,14 +181,14 @@ describe("EventTimeline", function() {
name: "Old Alice",
};
timeline.getState(EventTimeline.FORWARDS).getSentinelMember
.andCall(function(uid) {
.mockImplementation(function(uid) {
if (uid === userA) {
return sentinel;
}
return null;
});
timeline.getState(EventTimeline.BACKWARDS).getSentinelMember
.andCall(function(uid) {
.mockImplementation(function(uid) {
if (uid === userA) {
return oldSentinel;
}
@@ -229,14 +223,14 @@ describe("EventTimeline", function() {
name: "Old Alice",
};
timeline.getState(EventTimeline.FORWARDS).getSentinelMember
.andCall(function(uid) {
.mockImplementation(function(uid) {
if (uid === userA) {
return sentinel;
}
return null;
});
timeline.getState(EventTimeline.BACKWARDS).getSentinelMember
.andCall(function(uid) {
.mockImplementation(function(uid) {
if (uid === userA) {
return oldSentinel;
}
@@ -281,7 +275,7 @@ describe("EventTimeline", function() {
expect(events[1].forwardLooking).toBe(true);
expect(timeline.getState(EventTimeline.BACKWARDS).setStateEvents).
toNotHaveBeenCalled();
not.toHaveBeenCalled();
});
@@ -311,7 +305,7 @@ describe("EventTimeline", function() {
expect(events[1].forwardLooking).toBe(false);
expect(timeline.getState(EventTimeline.FORWARDS).setStateEvents).
toNotHaveBeenCalled();
not.toHaveBeenCalled();
});
});
+7 -13
View File
@@ -1,5 +1,6 @@
/*
Copyright 2017 New Vector Ltd
Copyright 2019 The Matrix.org Foundaction C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,19 +15,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import sdk from '../..';
const MatrixEvent = sdk.MatrixEvent;
import testUtils from '../test-utils';
import expect from 'expect';
import Promise from 'bluebird';
import {logger} from "../../src/logger";
import {MatrixEvent} from "../../src/models/event";
describe("MatrixEvent", () => {
beforeEach(function() {
testUtils.beforeEach(this); // eslint-disable-line no-invalid-this
});
describe(".attemptDecryption", () => {
let encryptedEvent;
@@ -44,21 +36,23 @@ describe("MatrixEvent", () => {
let callCount = 0;
let prom2;
let prom2Fulfilled = false;
const crypto = {
decryptEvent: function() {
++callCount;
console.log(`decrypt: ${callCount}`);
logger.log(`decrypt: ${callCount}`);
if (callCount == 1) {
// schedule a second decryption attempt while
// the first one is still running.
prom2 = encryptedEvent.attemptDecryption(crypto);
prom2.then(() => prom2Fulfilled = true);
const error = new Error("nope");
error.name = 'DecryptionError';
return Promise.reject(error);
} else {
expect(prom2.isFulfilled()).toBe(
expect(prom2Fulfilled).toBe(
false, 'second attemptDecryption resolved too soon');
return Promise.resolve({
+34
View File
@@ -0,0 +1,34 @@
import {FilterComponent} from "../../src/filter-component";
import {mkEvent} from '../test-utils';
describe("Filter Component", function() {
describe("types", function() {
it("should filter out events with other types", function() {
const filter = new FilterComponent({ types: ['m.room.message'] });
const event = mkEvent({
type: 'm.room.member',
content: { },
room: 'roomId',
event: true,
});
const checkResult = filter.check(event);
expect(checkResult).toBe(false);
});
it("should validate events with the same type", function() {
const filter = new FilterComponent({ types: ['m.room.message'] });
const event = mkEvent({
type: 'm.room.message',
content: { },
room: 'roomId',
event: true,
});
const checkResult = filter.check(event);
expect(checkResult).toBe(true);
});
});
});
+1 -8
View File
@@ -1,10 +1,4 @@
"use strict";
import 'source-map-support/register';
const sdk = require("../..");
const Filter = sdk.Filter;
const utils = require("../test-utils");
import expect from 'expect';
import {Filter} from "../../src/filter";
describe("Filter", function() {
const filterId = "f1lt3ring15g00d4ursoul";
@@ -12,7 +6,6 @@ describe("Filter", function() {
let filter;
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this
filter = new Filter(userId);
});
+57 -38
View File
@@ -1,5 +1,6 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,17 +14,10 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
"use strict";
import 'source-map-support/register';
import Promise from 'bluebird';
const sdk = require("../..");
const utils = require("../test-utils");
const InteractiveAuth = sdk.InteractiveAuth;
const MatrixError = sdk.MatrixError;
import expect from 'expect';
import {logger} from "../../src/logger";
import {InteractiveAuth} from "../../src/interactive-auth";
import {MatrixError} from "../../src/http-api";
// Trivial client object to test interactive auth
// (we do not need TestClient here)
@@ -34,13 +28,9 @@ class FakeClient {
}
describe("InteractiveAuth", function() {
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this
});
it("should start an auth stage and complete it", function(done) {
const doRequest = expect.createSpy();
const stateUpdated = expect.createSpy();
it("should start an auth stage and complete it", function() {
const doRequest = jest.fn();
const stateUpdated = jest.fn();
const ia = new InteractiveAuth({
matrixClient: new FakeClient(),
@@ -63,8 +53,8 @@ describe("InteractiveAuth", function() {
});
// first we expect a call here
stateUpdated.andCall(function(stage) {
console.log('aaaa');
stateUpdated.mockImplementation(function(stage) {
logger.log('aaaa');
expect(stage).toEqual("logintype");
ia.submitAuthDict({
type: "logintype",
@@ -74,8 +64,8 @@ describe("InteractiveAuth", function() {
// .. which should trigger a call here
const requestRes = {"a": "b"};
doRequest.andCall(function(authData) {
console.log('cccc');
doRequest.mockImplementation(function(authData) {
logger.log('cccc');
expect(authData).toEqual({
session: "sessionId",
type: "logintype",
@@ -84,16 +74,16 @@ describe("InteractiveAuth", function() {
return Promise.resolve(requestRes);
});
ia.attemptAuth().then(function(res) {
return ia.attemptAuth().then(function(res) {
expect(res).toBe(requestRes);
expect(doRequest.calls.length).toEqual(1);
expect(stateUpdated.calls.length).toEqual(1);
}).nodeify(done);
expect(doRequest).toBeCalledTimes(1);
expect(stateUpdated).toBeCalledTimes(1);
});
});
it("should make a request if no authdata is provided", function(done) {
const doRequest = expect.createSpy();
const stateUpdated = expect.createSpy();
it("should make a request if no authdata is provided", function() {
const doRequest = jest.fn();
const stateUpdated = jest.fn();
const ia = new InteractiveAuth({
matrixClient: new FakeClient(),
@@ -105,9 +95,9 @@ describe("InteractiveAuth", function() {
expect(ia.getStageParams("logintype")).toBe(undefined);
// first we expect a call to doRequest
doRequest.andCall(function(authData) {
console.log("request1", authData);
expect(authData).toEqual({});
doRequest.mockImplementation(function(authData) {
logger.log("request1", authData);
expect(authData).toEqual(null); // first request should be null
const err = new MatrixError({
session: "sessionId",
flows: [
@@ -123,7 +113,7 @@ describe("InteractiveAuth", function() {
// .. which should be followed by a call to stateUpdated
const requestRes = {"a": "b"};
stateUpdated.andCall(function(stage) {
stateUpdated.mockImplementation(function(stage) {
expect(stage).toEqual("logintype");
expect(ia.getSessionId()).toEqual("sessionId");
expect(ia.getStageParams("logintype")).toEqual({
@@ -131,8 +121,8 @@ describe("InteractiveAuth", function() {
});
// submitAuthDict should trigger another call to doRequest
doRequest.andCall(function(authData) {
console.log("request2", authData);
doRequest.mockImplementation(function(authData) {
logger.log("request2", authData);
expect(authData).toEqual({
session: "sessionId",
type: "logintype",
@@ -147,10 +137,39 @@ describe("InteractiveAuth", function() {
});
});
ia.attemptAuth().then(function(res) {
return ia.attemptAuth().then(function(res) {
expect(res).toBe(requestRes);
expect(doRequest.calls.length).toEqual(2);
expect(stateUpdated.calls.length).toEqual(1);
}).nodeify(done);
expect(doRequest).toBeCalledTimes(2);
expect(stateUpdated).toBeCalledTimes(1);
});
});
it("should start an auth stage and reject if no auth flow", function() {
const doRequest = jest.fn();
const stateUpdated = jest.fn();
const ia = new InteractiveAuth({
matrixClient: new FakeClient(),
doRequest: doRequest,
stateUpdated: stateUpdated,
});
doRequest.mockImplementation(function(authData) {
logger.log("request1", authData);
expect(authData).toEqual(null); // first request should be null
const err = new MatrixError({
session: "sessionId",
flows: [],
params: {
"logintype": { param: "aa" },
},
});
err.httpStatus = 401;
throw err;
});
return ia.attemptAuth().catch(function(error) {
expect(error.message).toBe('No appropriate authentication flow found');
});
});
});
+24
View File
@@ -0,0 +1,24 @@
import {TestClient} from '../TestClient';
describe('Login request', function() {
let client;
beforeEach(function() {
client = new TestClient();
});
afterEach(function() {
client.stop();
});
it('should store "access_token" and "user_id" if in response', async function() {
const response = { user_id: 1, access_token: Date.now().toString(16) };
client.httpBackend.when('POST', '/login').respond(200, response);
client.httpBackend.flush('/login', 1, 100);
await client.client.login('m.login.any', { user: 'test', password: '12312za' });
expect(client.client.getAccessToken()).toBe(response.access_token);
expect(client.client.getUserId()).toBe(response.user_id);
});
});
+62 -65
View File
@@ -1,12 +1,8 @@
"use strict";
import 'source-map-support/register';
import Promise from 'bluebird';
const sdk = require("../..");
const MatrixClient = sdk.MatrixClient;
const utils = require("../test-utils");
import {logger} from "../../src/logger";
import {MatrixClient} from "../../src/client";
import {Filter} from "../../src/filter";
import expect from 'expect';
import lolex from 'lolex';
jest.useFakeTimers();
describe("MatrixClient", function() {
const userId = "@alice:bar";
@@ -15,7 +11,6 @@ describe("MatrixClient", function() {
let client;
let store;
let scheduler;
let clock;
const KEEP_ALIVE_PATH = "/_matrix/client/versions";
@@ -69,7 +64,7 @@ describe("MatrixClient", function() {
"MatrixClient[UT] RECV " + method + " " + path + " " +
"EXPECT " + (next ? next.method : next) + " " + (next ? next.path : next)
);
console.log(logLine);
logger.log(logLine);
if (!next) { // no more things to return
if (pendingLookup) {
@@ -84,14 +79,14 @@ describe("MatrixClient", function() {
);
}
pendingLookup = {
promise: Promise.defer().promise,
promise: new Promise(() => {}),
method: method,
path: path,
};
return pendingLookup.promise;
}
if (next.path === path && next.method === method) {
console.log(
logger.log(
"MatrixClient[UT] Matched. Returning " +
(next.error ? "BAD" : "GOOD") + " response",
);
@@ -120,24 +115,26 @@ 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() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this
clock = lolex.install();
scheduler = [
"getQueueForEvent", "queueEvent", "removeEventFromQueue",
"setProcessFunction",
].reduce((r, k) => { r[k] = expect.createSpy(); return r; }, {});
].reduce((r, k) => { r[k] = jest.fn(); return r; }, {});
store = [
"getRoom", "getRooms", "getUser", "getSyncToken", "scrollback",
"save", "setSyncToken", "storeEvents", "storeRoom", "storeUser",
"save", "wantsSave", "setSyncToken", "storeEvents", "storeRoom", "storeUser",
"getFilterIdByName", "setFilterIdByName", "getFilter", "storeFilter",
"getSyncAccumulator", "startup", "deleteAllData",
].reduce((r, k) => { r[k] = expect.createSpy(); return r; }, {});
store.getSavedSync = expect.createSpy().andReturn(Promise.resolve(null));
store.setSyncData = expect.createSpy().andReturn(Promise.resolve(null));
].reduce((r, k) => { r[k] = jest.fn(); return r; }, {});
store.getSavedSync = jest.fn().mockReturnValue(Promise.resolve(null));
store.getSavedSyncToken = jest.fn().mockReturnValue(Promise.resolve(null));
store.setSyncData = jest.fn().mockReturnValue(Promise.resolve(null));
store.getClientOptions = jest.fn().mockReturnValue(Promise.resolve(null));
store.storeClientOptions = jest.fn().mockReturnValue(Promise.resolve(null));
store.isNewlyCreated = jest.fn().mockReturnValue(Promise.resolve(true));
client = new MatrixClient({
baseUrl: "https://my.home.server",
idBaseUrl: identityServerUrl,
@@ -149,13 +146,10 @@ describe("MatrixClient", function() {
});
// FIXME: We shouldn't be yanking _http like this.
client._http = [
"authedRequest", "authedRequestWithPrefix", "getContentUri",
"request", "requestWithPrefix", "uploadContent",
].reduce((r, k) => { r[k] = expect.createSpy(); return r; }, {});
client._http.authedRequest.andCall(httpReq);
client._http.authedRequestWithPrefix.andCall(httpReq);
client._http.requestWithPrefix.andCall(httpReq);
client._http.request.andCall(httpReq);
"authedRequest", "getContentUri", "request", "uploadContent",
].reduce((r, k) => { r[k] = jest.fn(); return r; }, {});
client._http.authedRequest.mockImplementation(httpReq);
client._http.request.mockImplementation(httpReq);
// set reasonable working defaults
acceptKeepalives = true;
@@ -167,38 +161,38 @@ describe("MatrixClient", function() {
});
afterEach(function() {
clock.uninstall();
// need to re-stub the requests with NOPs because there are no guarantees
// clients from previous tests will be GC'd before the next test. This
// means they may call /events and then fail an expect() which will fail
// a DIFFERENT test (pollution between tests!) - we return unresolved
// promises to stop the client from continuing to run.
client._http.authedRequest.andCall(function() {
return Promise.defer().promise;
});
client._http.authedRequestWithPrefix.andCall(function() {
return Promise.defer().promise;
client._http.authedRequest.mockImplementation(function() {
return new Promise(() => {});
});
});
it("should not POST /filter if a matching filter already exists", function(done) {
it("should not POST /filter if a matching filter already exists", async function() {
httpLookups = [];
httpLookups.push(PUSH_RULES_RESPONSE);
httpLookups.push(SYNC_RESPONSE);
const filterId = "ehfewf";
store.getFilterIdByName.andReturn(filterId);
const filter = new sdk.Filter(0, filterId);
store.getFilterIdByName.mockReturnValue(filterId);
const filter = new Filter(0, filterId);
filter.setDefinition({"room": {"timeline": {"limit": 8}}});
store.getFilter.andReturn(filter);
client.startClient();
client.on("sync", function syncListener(state) {
if (state === "SYNCING") {
expect(httpLookups.length).toEqual(0);
client.removeListener("sync", syncListener);
done();
}
store.getFilter.mockReturnValue(filter);
const syncPromise = new Promise((resolve, reject) => {
client.on("sync", function syncListener(state) {
if (state === "SYNCING") {
expect(httpLookups.length).toEqual(0);
client.removeListener("sync", syncListener);
resolve();
} else if (state === "ERROR") {
reject(new Error("sync error"));
}
});
});
await client.startClient();
await syncPromise;
});
describe("getSyncState", function() {
@@ -206,15 +200,18 @@ describe("MatrixClient", function() {
expect(client.getSyncState()).toBe(null);
});
it("should return the same sync state as emitted sync events", function(done) {
client.on("sync", function syncListener(state) {
expect(state).toEqual(client.getSyncState());
if (state === "SYNCING") {
client.removeListener("sync", syncListener);
done();
}
it("should return the same sync state as emitted sync events", async function() {
const syncingPromise = new Promise((resolve) => {
client.on("sync", function syncListener(state) {
expect(state).toEqual(client.getSyncState());
if (state === "SYNCING") {
client.removeListener("sync", syncListener);
resolve();
}
});
});
client.startClient();
await client.startClient();
await syncingPromise;
});
});
@@ -243,11 +240,11 @@ describe("MatrixClient", function() {
},
});
httpLookups.push(FILTER_RESPONSE);
store.getFilterIdByName.andReturn(invalidFilterId);
store.getFilterIdByName.mockReturnValue(invalidFilterId);
const filterName = getFilterName(client.credentials.userId);
client.store.setFilterIdByName(filterName, invalidFilterId);
const filter = new sdk.Filter(client.credentials.userId);
const filter = new Filter(client.credentials.userId);
client.getOrCreateFilter(filterName, filter).then(function(filterId) {
expect(filterId).toEqual(FILTER_RESPONSE.data.filter_id);
@@ -257,8 +254,8 @@ describe("MatrixClient", function() {
});
describe("retryImmediately", function() {
it("should return false if there is no request waiting", function() {
client.startClient();
it("should return false if there is no request waiting", async function() {
await client.startClient();
expect(client.retryImmediately()).toBe(false);
});
@@ -275,7 +272,7 @@ describe("MatrixClient", function() {
if (state === "ERROR" && httpLookups.length > 0) {
expect(httpLookups.length).toEqual(2);
expect(client.retryImmediately()).toBe(true);
clock.tick(1);
jest.advanceTimersByTime(1);
} else if (state === "PREPARED" && httpLookups.length === 0) {
client.removeListener("sync", syncListener);
done();
@@ -301,9 +298,9 @@ describe("MatrixClient", function() {
expect(client.retryImmediately()).toBe(
true, "retryImmediately returned false",
);
clock.tick(1);
jest.advanceTimersByTime(1);
} else if (state === "RECONNECTING" && httpLookups.length > 0) {
clock.tick(10000);
jest.advanceTimersByTime(10000);
} else if (state === "SYNCING" && httpLookups.length === 0) {
client.removeListener("sync", syncListener);
done();
@@ -325,7 +322,7 @@ describe("MatrixClient", function() {
if (state === "ERROR" && httpLookups.length > 0) {
expect(httpLookups.length).toEqual(3);
expect(client.retryImmediately()).toBe(true);
clock.tick(1);
jest.advanceTimersByTime(1);
} else if (state === "PREPARED" && httpLookups.length === 0) {
client.removeListener("sync", syncListener);
done();
@@ -342,7 +339,7 @@ describe("MatrixClient", function() {
function syncChecker(expectedStates, done) {
return function syncListener(state, old) {
const expected = expectedStates.shift();
console.log(
logger.log(
"'sync' curr=%s old=%s EXPECT=%s", state, old, expected,
);
if (!expected) {
@@ -356,7 +353,7 @@ describe("MatrixClient", function() {
done();
}
// standard retry time is 5 to 10 seconds
clock.tick(10000);
jest.advanceTimersByTime(10000);
};
}
@@ -379,7 +376,7 @@ describe("MatrixClient", function() {
client.startClient();
});
it("should transition ERROR -> PREPARED after /sync if prev failed",
it("should transition ERROR -> CATCHUP after /sync if prev failed",
function(done) {
const expectedStates = [];
acceptKeepalives = false;
@@ -402,7 +399,7 @@ describe("MatrixClient", function() {
expectedStates.push(["RECONNECTING", null]);
expectedStates.push(["ERROR", "RECONNECTING"]);
expectedStates.push(["PREPARED", "ERROR"]);
expectedStates.push(["CATCHUP", "ERROR"]);
client.on("sync", syncChecker(expectedStates, done));
client.startClient();
});
+5 -6
View File
@@ -1,9 +1,5 @@
"use strict";
import 'source-map-support/register';
const PushProcessor = require("../../lib/pushprocessor");
const utils = require("../test-utils");
import expect from 'expect';
import * as utils from "../test-utils";
import {PushProcessor} from "../../src/pushprocessor";
describe('NotificationService', function() {
const testUserId = "@ali:matrix.org";
@@ -24,6 +20,9 @@ describe('NotificationService', function() {
name: testDisplayName,
};
},
getJoinedMemberCount: function() {
return 0;
},
members: {},
},
};
+45 -57
View File
@@ -1,53 +1,43 @@
"use strict";
import * as callbacks from "../../src/realtime-callbacks";
import 'source-map-support/register';
const callbacks = require("../../lib/realtime-callbacks");
const testUtils = require("../test-utils.js");
import expect from 'expect';
import lolex from 'lolex';
let wallTime = 1234567890;
jest.useFakeTimers();
describe("realtime-callbacks", function() {
let clock;
function tick(millis) {
clock.tick(millis);
wallTime += millis;
jest.advanceTimersByTime(millis);
}
beforeEach(function() {
testUtils.beforeEach(this); // eslint-disable-line no-invalid-this
clock = lolex.install();
const fakeDate = clock.Date;
callbacks.setNow(fakeDate.now.bind(fakeDate));
callbacks.setNow(() => wallTime);
});
afterEach(function() {
callbacks.setNow();
clock.uninstall();
});
describe("setTimeout", function() {
it("should call the callback after the timeout", function() {
const callback = expect.createSpy();
const callback = jest.fn();
callbacks.setTimeout(callback, 100);
expect(callback).toNotHaveBeenCalled();
expect(callback).not.toHaveBeenCalled();
tick(100);
expect(callback).toHaveBeenCalled();
});
it("should default to a zero timeout", function() {
const callback = expect.createSpy();
const callback = jest.fn();
callbacks.setTimeout(callback);
expect(callback).toNotHaveBeenCalled();
expect(callback).not.toHaveBeenCalled();
tick(0);
expect(callback).toHaveBeenCalled();
});
it("should pass any parameters to the callback", function() {
const callback = expect.createSpy();
const callback = jest.fn();
callbacks.setTimeout(callback, 0, "a", "b", "c");
tick(0);
expect(callback).toHaveBeenCalledWith("a", "b", "c");
@@ -56,8 +46,8 @@ describe("realtime-callbacks", function() {
it("should set 'this' to the global object", function() {
let passed = false;
const callback = function() {
expect(this).toBe(global); // eslint-disable-line no-invalid-this
expect(this.console).toBeTruthy(); // eslint-disable-line no-invalid-this
expect(this).toBe(global); // eslint-disable-line babel/no-invalid-this
expect(this.console).toBeTruthy(); // eslint-disable-line babel/no-invalid-this
passed = true;
};
callbacks.setTimeout(callback);
@@ -66,10 +56,10 @@ describe("realtime-callbacks", function() {
});
it("should handle timeouts of several seconds", function() {
const callback = expect.createSpy();
const callback = jest.fn();
callbacks.setTimeout(callback, 2000);
expect(callback).toNotHaveBeenCalled();
expect(callback).not.toHaveBeenCalled();
for (let i = 0; i < 4; i++) {
tick(500);
}
@@ -77,24 +67,24 @@ describe("realtime-callbacks", function() {
});
it("should call multiple callbacks in the right order", function() {
const callback1 = expect.createSpy();
const callback2 = expect.createSpy();
const callback3 = expect.createSpy();
const callback1 = jest.fn();
const callback2 = jest.fn();
const callback3 = jest.fn();
callbacks.setTimeout(callback2, 200);
callbacks.setTimeout(callback1, 100);
callbacks.setTimeout(callback3, 300);
expect(callback1).toNotHaveBeenCalled();
expect(callback2).toNotHaveBeenCalled();
expect(callback3).toNotHaveBeenCalled();
expect(callback1).not.toHaveBeenCalled();
expect(callback2).not.toHaveBeenCalled();
expect(callback3).not.toHaveBeenCalled();
tick(100);
expect(callback1).toHaveBeenCalled();
expect(callback2).toNotHaveBeenCalled();
expect(callback3).toNotHaveBeenCalled();
expect(callback2).not.toHaveBeenCalled();
expect(callback3).not.toHaveBeenCalled();
tick(100);
expect(callback1).toHaveBeenCalled();
expect(callback2).toHaveBeenCalled();
expect(callback3).toNotHaveBeenCalled();
expect(callback3).not.toHaveBeenCalled();
tick(100);
expect(callback1).toHaveBeenCalled();
expect(callback2).toHaveBeenCalled();
@@ -102,35 +92,34 @@ describe("realtime-callbacks", function() {
});
it("should treat -ve timeouts the same as a zero timeout", function() {
const callback1 = expect.createSpy();
const callback2 = expect.createSpy();
const callback1 = jest.fn();
const callback2 = jest.fn();
// check that cb1 is called before cb2
callback1.andCall(function() {
expect(callback2).toNotHaveBeenCalled();
callback1.mockImplementation(function() {
expect(callback2).not.toHaveBeenCalled();
});
callbacks.setTimeout(callback1);
callbacks.setTimeout(callback2, -100);
expect(callback1).toNotHaveBeenCalled();
expect(callback2).toNotHaveBeenCalled();
expect(callback1).not.toHaveBeenCalled();
expect(callback2).not.toHaveBeenCalled();
tick(0);
expect(callback1).toHaveBeenCalled();
expect(callback2).toHaveBeenCalled();
});
it("should not get confused by chained calls", function() {
const callback2 = expect.createSpy();
const callback1 = expect.createSpy();
callback1.andCall(function() {
const callback2 = jest.fn();
const callback1 = jest.fn(function() {
callbacks.setTimeout(callback2, 0);
expect(callback2).toNotHaveBeenCalled();
expect(callback2).not.toHaveBeenCalled();
});
callbacks.setTimeout(callback1);
expect(callback1).toNotHaveBeenCalled();
expect(callback2).toNotHaveBeenCalled();
expect(callback1).not.toHaveBeenCalled();
expect(callback2).not.toHaveBeenCalled();
tick(0);
expect(callback1).toHaveBeenCalled();
// the fake timer won't actually run callbacks registered during
@@ -140,16 +129,15 @@ describe("realtime-callbacks", function() {
});
it("should be immune to exceptions", function() {
const callback1 = expect.createSpy();
callback1.andCall(function() {
const callback1 = jest.fn(function() {
throw new Error("prepare to die");
});
const callback2 = expect.createSpy();
const callback2 = jest.fn();
callbacks.setTimeout(callback1, 0);
callbacks.setTimeout(callback2, 0);
expect(callback1).toNotHaveBeenCalled();
expect(callback2).toNotHaveBeenCalled();
expect(callback1).not.toHaveBeenCalled();
expect(callback2).not.toHaveBeenCalled();
tick(0);
expect(callback1).toHaveBeenCalled();
expect(callback2).toHaveBeenCalled();
@@ -158,16 +146,16 @@ describe("realtime-callbacks", function() {
describe("cancelTimeout", function() {
it("should cancel a pending timeout", function() {
const callback = expect.createSpy();
const callback = jest.fn();
const k = callbacks.setTimeout(callback);
callbacks.clearTimeout(k);
tick(0);
expect(callback).toNotHaveBeenCalled();
expect(callback).not.toHaveBeenCalled();
});
it("should not affect sooner timeouts", function() {
const callback1 = expect.createSpy();
const callback2 = expect.createSpy();
const callback1 = jest.fn();
const callback2 = jest.fn();
callbacks.setTimeout(callback1, 100);
const k = callbacks.setTimeout(callback2, 200);
@@ -175,10 +163,10 @@ describe("realtime-callbacks", function() {
tick(100);
expect(callback1).toHaveBeenCalled();
expect(callback2).toNotHaveBeenCalled();
expect(callback2).not.toHaveBeenCalled();
tick(150);
expect(callback2).toNotHaveBeenCalled();
expect(callback2).not.toHaveBeenCalled();
});
});
});
+61 -11
View File
@@ -1,10 +1,5 @@
"use strict";
import 'source-map-support/register';
const sdk = require("../..");
const RoomMember = sdk.RoomMember;
const utils = require("../test-utils");
import expect from 'expect';
import * as utils from "../test-utils";
import {RoomMember} from "../../src/models/room-member";
describe("RoomMember", function() {
const roomId = "!foo:bar";
@@ -14,7 +9,6 @@ describe("RoomMember", function() {
let member;
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this
member = new RoomMember(roomId, userA);
});
@@ -36,7 +30,7 @@ describe("RoomMember", function() {
const url = member.getAvatarUrl(hsUrl);
// we don't care about how the mxc->http conversion is done, other
// than it contains the mxc body.
expect(url.indexOf("flibble/wibble")).toNotEqual(-1);
expect(url.indexOf("flibble/wibble")).not.toEqual(-1);
});
it("should return an identicon HTTP URL if allowDefault was set and there " +
@@ -192,6 +186,15 @@ describe("RoomMember", function() {
});
});
describe("isOutOfBand", function() {
it("should be set by markOutOfBand", function() {
const member = new RoomMember();
expect(member.isOutOfBand()).toEqual(false);
member.markOutOfBand();
expect(member.isOutOfBand()).toEqual(true);
});
});
describe("setMembershipEvent", function() {
const joinEvent = utils.mkMembership({
event: true,
@@ -246,9 +249,9 @@ describe("RoomMember", function() {
member.setMembershipEvent(joinEvent);
expect(member.name).toEqual("Alice"); // prefer displayname
member.setMembershipEvent(joinEvent, roomState);
expect(member.name).toNotEqual("Alice"); // it should disambig.
expect(member.name).not.toEqual("Alice"); // it should disambig.
// user_id should be there somewhere
expect(member.name.indexOf(userA)).toNotEqual(-1);
expect(member.name.indexOf(userA)).not.toEqual(-1);
});
it("should emit 'RoomMember.membership' if the membership changes", function() {
@@ -276,5 +279,52 @@ describe("RoomMember", function() {
member.setMembershipEvent(joinEvent); // no-op
expect(emitCount).toEqual(1);
});
it("should set 'name' to user_id if it is just whitespace", function() {
const joinEvent = utils.mkMembership({
event: true,
mship: "join",
user: userA,
room: roomId,
name: " \u200b ",
});
expect(member.name).toEqual(userA); // default = user_id
member.setMembershipEvent(joinEvent);
expect(member.name).toEqual(userA); // it should fallback because all whitespace
});
it("should disambiguate users on a fuzzy displayname match", function() {
const joinEvent = utils.mkMembership({
event: true,
mship: "join",
user: userA,
room: roomId,
name: "Alíce\u200b", // note diacritic and zero width char
});
const roomState = {
getStateEvents: function(type) {
if (type !== "m.room.member") {
return [];
}
return [
utils.mkMembership({
event: true, mship: "join", room: roomId,
user: userC, name: "Alice",
}),
joinEvent,
];
},
getUserIdsWithDisplayName: function(displayName) {
return [userA, userC];
},
};
expect(member.name).toEqual(userA); // default = user_id
member.setMembershipEvent(joinEvent, roomState);
expect(member.name).not.toEqual("Alíce"); // it should disambig.
// user_id should be there somewhere
expect(member.name.indexOf(userA)).not.toEqual(-1);
});
});
});
+231 -31
View File
@@ -1,20 +1,17 @@
"use strict";
import 'source-map-support/register';
const sdk = require("../..");
const RoomState = sdk.RoomState;
const RoomMember = sdk.RoomMember;
const utils = require("../test-utils");
import expect from 'expect';
import * as utils from "../test-utils";
import {RoomState} from "../../src/models/room-state";
import {RoomMember} from "../../src/models/room-member";
describe("RoomState", function() {
const roomId = "!foo:bar";
const userA = "@alice:bar";
const userB = "@bob:bar";
const userC = "@cleo:bar";
const userLazy = "@lazy:bar";
let state;
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this
state = new RoomState(roomId);
state.setStateEvents([
utils.mkMembership({ // userA joined
@@ -46,8 +43,8 @@ describe("RoomState", function() {
const members = state.getMembers();
expect(members.length).toEqual(2);
// ordering unimportant
expect([userA, userB].indexOf(members[0].userId)).toNotEqual(-1);
expect([userA, userB].indexOf(members[1].userId)).toNotEqual(-1);
expect([userA, userB].indexOf(members[0].userId)).not.toEqual(-1);
expect([userA, userB].indexOf(members[1].userId)).not.toEqual(-1);
});
});
@@ -78,8 +75,8 @@ describe("RoomState", function() {
});
describe("getSentinelMember", function() {
it("should return null if there is no member", function() {
expect(state.getSentinelMember("@no-one:here")).toEqual(null);
it("should return a member with the user id as name", function() {
expect(state.getSentinelMember("@no-one:here").name).toEqual("@no-one:here");
});
it("should return a member which doesn't change when the state is updated",
@@ -117,8 +114,8 @@ describe("RoomState", function() {
const events = state.getStateEvents("m.room.member");
expect(events.length).toEqual(2);
// ordering unimportant
expect([userA, userB].indexOf(events[0].getStateKey())).toNotEqual(-1);
expect([userA, userB].indexOf(events[1].getStateKey())).toNotEqual(-1);
expect([userA, userB].indexOf(events[0].getStateKey())).not.toEqual(-1);
expect([userA, userB].indexOf(events[1].getStateKey())).not.toEqual(-1);
});
it("should return a single MatrixEvent if a state_key was specified",
@@ -162,6 +159,7 @@ describe("RoomState", function() {
];
let emitCount = 0;
state.on("RoomState.newMember", function(ev, st, mem) {
expect(state.getMember(mem.userId)).toEqual(mem);
expect(mem.userId).toEqual(memberEvents[emitCount].getSender());
expect(mem.membership).toBeFalsy(); // not defined yet
emitCount += 1;
@@ -222,7 +220,6 @@ describe("RoomState", function() {
it("should call setPowerLevelEvent on a new RoomMember if power levels exist",
function() {
const userC = "@cleo:bar";
const memberEvent = utils.mkMembership({
mship: "join", user: userC, room: roomId, event: true,
});
@@ -255,13 +252,121 @@ describe("RoomState", function() {
});
state.setStateEvents([memberEvent]);
expect(state.members[userA].setMembershipEvent).toNotHaveBeenCalled();
expect(state.members[userA].setMembershipEvent).not.toHaveBeenCalled();
expect(state.members[userB].setMembershipEvent).toHaveBeenCalledWith(
memberEvent, state,
);
});
});
describe("setOutOfBandMembers", function() {
it("should add a new member", function() {
const oobMemberEvent = utils.mkMembership({
user: userLazy, mship: "join", room: roomId, event: true,
});
state.markOutOfBandMembersStarted();
state.setOutOfBandMembers([oobMemberEvent]);
const member = state.getMember(userLazy);
expect(member.userId).toEqual(userLazy);
expect(member.isOutOfBand()).toEqual(true);
});
it("should have no effect when not in correct status", function() {
state.setOutOfBandMembers([utils.mkMembership({
user: userLazy, mship: "join", room: roomId, event: true,
})]);
expect(state.getMember(userLazy)).toBeFalsy();
});
it("should emit newMember when adding a member", function() {
const userLazy = "@oob:hs";
const oobMemberEvent = utils.mkMembership({
user: userLazy, mship: "join", room: roomId, event: true,
});
let eventReceived = false;
state.once('RoomState.newMember', (_, __, member) => {
expect(member.userId).toEqual(userLazy);
eventReceived = true;
});
state.markOutOfBandMembersStarted();
state.setOutOfBandMembers([oobMemberEvent]);
expect(eventReceived).toEqual(true);
});
it("should never overwrite existing members", function() {
const oobMemberEvent = utils.mkMembership({
user: userA, mship: "join", room: roomId, event: true,
});
state.markOutOfBandMembersStarted();
state.setOutOfBandMembers([oobMemberEvent]);
const memberA = state.getMember(userA);
expect(memberA.events.member.getId()).not.toEqual(oobMemberEvent.getId());
expect(memberA.isOutOfBand()).toEqual(false);
});
it("should emit members when updating a member", function() {
const doesntExistYetUserId = "@doesntexistyet:hs";
const oobMemberEvent = utils.mkMembership({
user: doesntExistYetUserId, mship: "join", room: roomId, event: true,
});
let eventReceived = false;
state.once('RoomState.members', (_, __, member) => {
expect(member.userId).toEqual(doesntExistYetUserId);
eventReceived = true;
});
state.markOutOfBandMembersStarted();
state.setOutOfBandMembers([oobMemberEvent]);
expect(eventReceived).toEqual(true);
});
});
describe("clone", function() {
it("should contain same information as original", function() {
// include OOB members in copy
state.markOutOfBandMembersStarted();
state.setOutOfBandMembers([utils.mkMembership({
user: userLazy, mship: "join", room: roomId, event: true,
})]);
const copy = state.clone();
// check individual members
[userA, userB, userLazy].forEach((userId) => {
const member = state.getMember(userId);
const memberCopy = copy.getMember(userId);
expect(member.name).toEqual(memberCopy.name);
expect(member.isOutOfBand()).toEqual(memberCopy.isOutOfBand());
});
// check member keys
expect(Object.keys(state.members)).toEqual(Object.keys(copy.members));
// check join count
expect(state.getJoinedMemberCount()).toEqual(copy.getJoinedMemberCount());
});
it("should mark old copy as not waiting for out of band anymore", function() {
state.markOutOfBandMembersStarted();
const copy = state.clone();
copy.setOutOfBandMembers([utils.mkMembership({
user: userA, mship: "join", room: roomId, event: true,
})]);
// should have no effect as it should be marked in status finished just like copy
state.setOutOfBandMembers([utils.mkMembership({
user: userLazy, mship: "join", room: roomId, event: true,
})]);
expect(state.getMember(userLazy)).toBeFalsy();
});
it("should return copy independent of original", function() {
const copy = state.clone();
copy.setStateEvents([utils.mkMembership({
user: userLazy, mship: "join", room: roomId, event: true,
})]);
expect(state.getMember(userLazy)).toBeFalsy();
expect(state.getJoinedMemberCount()).toEqual(2);
expect(copy.getJoinedMemberCount()).toEqual(3);
});
});
describe("setTypingEvent", function() {
it("should call setTypingEvent on each RoomMember", function() {
const typingEvent = utils.mkEvent({
@@ -284,13 +389,6 @@ describe("RoomState", function() {
});
describe("maySendStateEvent", function() {
it("should say non-joined members may not send state",
function() {
expect(state.maySendStateEvent(
'm.room.name', "@nobody:nowhere",
)).toEqual(false);
});
it("should say any member may send state with no power level event",
function() {
expect(state.maySendStateEvent('m.room.name', userA)).toEqual(true);
@@ -366,15 +464,117 @@ describe("RoomState", function() {
});
});
describe("maySendEvent", function() {
it("should say non-joined members may not send events",
function() {
expect(state.maySendEvent(
'm.room.message', "@nobody:nowhere",
)).toEqual(false);
expect(state.maySendMessage("@nobody:nowhere")).toEqual(false);
describe("getJoinedMemberCount", function() {
beforeEach(() => {
state = new RoomState(roomId);
});
it("should update after adding joined member", function() {
state.setStateEvents([
utils.mkMembership({event: true, mship: "join",
user: userA, room: roomId}),
]);
expect(state.getJoinedMemberCount()).toEqual(1);
state.setStateEvents([
utils.mkMembership({event: true, mship: "join",
user: userC, room: roomId}),
]);
expect(state.getJoinedMemberCount()).toEqual(2);
});
});
describe("getInvitedMemberCount", function() {
beforeEach(() => {
state = new RoomState(roomId);
});
it("should update after adding invited member", function() {
state.setStateEvents([
utils.mkMembership({event: true, mship: "invite",
user: userA, room: roomId}),
]);
expect(state.getInvitedMemberCount()).toEqual(1);
state.setStateEvents([
utils.mkMembership({event: true, mship: "invite",
user: userC, room: roomId}),
]);
expect(state.getInvitedMemberCount()).toEqual(2);
});
});
describe("setJoinedMemberCount", function() {
beforeEach(() => {
state = new RoomState(roomId);
});
it("should, once used, override counting members from state", function() {
state.setStateEvents([
utils.mkMembership({event: true, mship: "join",
user: userA, room: roomId}),
]);
expect(state.getJoinedMemberCount()).toEqual(1);
state.setJoinedMemberCount(100);
expect(state.getJoinedMemberCount()).toEqual(100);
state.setStateEvents([
utils.mkMembership({event: true, mship: "join",
user: userC, room: roomId}),
]);
expect(state.getJoinedMemberCount()).toEqual(100);
});
it("should, once used, override counting members from state, " +
"also after clone", function() {
state.setStateEvents([
utils.mkMembership({event: true, mship: "join",
user: userA, room: roomId}),
]);
state.setJoinedMemberCount(100);
const copy = state.clone();
copy.setStateEvents([
utils.mkMembership({event: true, mship: "join",
user: userC, room: roomId}),
]);
expect(state.getJoinedMemberCount()).toEqual(100);
});
});
describe("setInvitedMemberCount", function() {
beforeEach(() => {
state = new RoomState(roomId);
});
it("should, once used, override counting members from state", function() {
state.setStateEvents([
utils.mkMembership({event: true, mship: "invite",
user: userB, room: roomId}),
]);
expect(state.getInvitedMemberCount()).toEqual(1);
state.setInvitedMemberCount(100);
expect(state.getInvitedMemberCount()).toEqual(100);
state.setStateEvents([
utils.mkMembership({event: true, mship: "invite",
user: userC, room: roomId}),
]);
expect(state.getInvitedMemberCount()).toEqual(100);
});
it("should, once used, override counting members from state, " +
"also after clone", function() {
state.setStateEvents([
utils.mkMembership({event: true, mship: "invite",
user: userB, room: roomId}),
]);
state.setInvitedMemberCount(100);
const copy = state.clone();
copy.setStateEvents([
utils.mkMembership({event: true, mship: "invite",
user: userC, room: roomId}),
]);
expect(state.getInvitedMemberCount()).toEqual(100);
});
});
describe("maySendEvent", function() {
it("should say any member may send events with no power level event",
function() {
expect(state.maySendEvent('m.room.message', userA)).toEqual(true);
+363 -180
View File
@@ -1,14 +1,8 @@
"use strict";
import 'source-map-support/register';
const sdk = require("../..");
const Room = sdk.Room;
const RoomState = sdk.RoomState;
const MatrixEvent = sdk.MatrixEvent;
const EventStatus = sdk.EventStatus;
const EventTimeline = sdk.EventTimeline;
const utils = require("../test-utils");
import expect from 'expect';
import * as utils from "../test-utils";
import {EventStatus, MatrixEvent} from "../../src/models/event";
import {EventTimeline} from "../../src/models/event-timeline";
import {RoomState} from "../../src/models/room-state";
import {Room} from "../../src/models/room";
describe("Room", function() {
const roomId = "!foo:bar";
@@ -19,20 +13,19 @@ describe("Room", function() {
let room;
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this
room = new Room(roomId);
// mock RoomStates
room.oldState = room.getLiveTimeline()._startState =
utils.mock(sdk.RoomState, "oldState");
utils.mock(RoomState, "oldState");
room.currentState = room.getLiveTimeline()._endState =
utils.mock(sdk.RoomState, "currentState");
utils.mock(RoomState, "currentState");
});
describe("getAvatarUrl", function() {
const hsUrl = "https://my.home.server";
it("should return the URL from m.room.avatar preferentially", function() {
room.currentState.getStateEvents.andCall(function(type, key) {
room.currentState.getStateEvents.mockImplementation(function(type, key) {
if (type === "m.room.avatar" && key === "") {
return utils.mkEvent({
event: true,
@@ -49,7 +42,7 @@ describe("Room", function() {
const url = room.getAvatarUrl(hsUrl);
// we don't care about how the mxc->http conversion is done, other
// than it contains the mxc body.
expect(url.indexOf("flibble/wibble")).toNotEqual(-1);
expect(url.indexOf("flibble/wibble")).not.toEqual(-1);
});
it("should return an identicon HTTP URL if allowDefault was set and there " +
@@ -67,13 +60,14 @@ describe("Room", function() {
describe("getMember", function() {
beforeEach(function() {
// clobber members property with test data
room.currentState.members = {
"@alice:bar": {
userId: userA,
roomId: roomId,
},
};
room.currentState.getMember.mockImplementation(function(userId) {
return {
"@alice:bar": {
userId: userA,
roomId: roomId,
},
}[userId] || null;
});
});
it("should return null if the member isn't in current state", function() {
@@ -81,7 +75,7 @@ describe("Room", function() {
});
it("should return the member from current state", function() {
expect(room.getMember(userA)).toNotEqual(null);
expect(room.getMember(userA)).not.toEqual(null);
});
});
@@ -103,7 +97,7 @@ describe("Room", function() {
user_ids: [userA],
},
});
room.addLiveEvents([typing]);
room.addEphemeralEvents([typing]);
expect(room.currentState.setTypingEvent).toHaveBeenCalledWith(typing);
});
@@ -173,7 +167,7 @@ describe("Room", function() {
);
expect(events[0].forwardLooking).toBe(true);
expect(events[1].forwardLooking).toBe(true);
expect(room.oldState.setStateEvents).toNotHaveBeenCalled();
expect(room.oldState.setStateEvents).not.toHaveBeenCalled();
});
it("should synthesize read receipts for the senders of events", function() {
@@ -182,7 +176,7 @@ describe("Room", function() {
membership: "join",
name: "Alice",
};
room.currentState.getSentinelMember.andCall(function(uid) {
room.currentState.getSentinelMember.mockImplementation(function(uid) {
if (uid === userA) {
return sentinel;
}
@@ -291,13 +285,13 @@ describe("Room", function() {
membership: "join",
name: "Old Alice",
};
room.currentState.getSentinelMember.andCall(function(uid) {
room.currentState.getSentinelMember.mockImplementation(function(uid) {
if (uid === userA) {
return sentinel;
}
return null;
});
room.oldState.getSentinelMember.andCall(function(uid) {
room.oldState.getSentinelMember.mockImplementation(function(uid) {
if (uid === userA) {
return oldSentinel;
}
@@ -330,13 +324,13 @@ describe("Room", function() {
membership: "join",
name: "Old Alice",
};
room.currentState.getSentinelMember.andCall(function(uid) {
room.currentState.getSentinelMember.mockImplementation(function(uid) {
if (uid === userA) {
return sentinel;
}
return null;
});
room.oldState.getSentinelMember.andCall(function(uid) {
room.oldState.getSentinelMember.mockImplementation(function(uid) {
if (uid === userA) {
return oldSentinel;
}
@@ -378,27 +372,30 @@ describe("Room", function() {
);
expect(events[0].forwardLooking).toBe(false);
expect(events[1].forwardLooking).toBe(false);
expect(room.currentState.setStateEvents).toNotHaveBeenCalled();
expect(room.currentState.setStateEvents).not.toHaveBeenCalled();
});
});
const resetTimelineTests = function(timelineSupport) {
const events = [
utils.mkMessage({
room: roomId, user: userA, msg: "A message", event: true,
}),
utils.mkEvent({
type: "m.room.name", room: roomId, user: userA, event: true,
content: { name: "New Room Name" },
}),
utils.mkEvent({
type: "m.room.name", room: roomId, user: userA, event: true,
content: { name: "Another New Name" },
}),
];
let events = null;
beforeEach(function() {
room = new Room(roomId, {timelineSupport: timelineSupport});
room = new Room(roomId, null, null, {timelineSupport: timelineSupport});
// set events each time to avoid resusing Event objects (which
// doesn't work because they get frozen)
events = [
utils.mkMessage({
room: roomId, user: userA, msg: "A message", event: true,
}),
utils.mkEvent({
type: "m.room.name", room: roomId, user: userA, event: true,
content: { name: "New Room Name" },
}),
utils.mkEvent({
type: "m.room.name", room: roomId, user: userA, event: true,
content: { name: "Another New Name" },
}),
];
});
it("should copy state from previous timeline", function() {
@@ -465,7 +462,7 @@ describe("Room", function() {
describe("compareEventOrdering", function() {
beforeEach(function() {
room = new Room(roomId, {timelineSupport: true});
room = new Room(roomId, null, null, {timelineSupport: true});
});
const events = [
@@ -541,7 +538,7 @@ describe("Room", function() {
describe("getJoinedMembers", function() {
it("should return members whose membership is 'join'", function() {
room.currentState.getMembers.andCall(function() {
room.currentState.getMembers.mockImplementation(function() {
return [
{ userId: "@alice:bar", membership: "join" },
{ userId: "@bob:bar", membership: "invite" },
@@ -554,7 +551,7 @@ describe("Room", function() {
});
it("should return an empty list if no membership is 'join'", function() {
room.currentState.getMembers.andCall(function() {
room.currentState.getMembers.mockImplementation(function() {
return [
{ userId: "@bob:bar", membership: "invite" },
];
@@ -567,72 +564,72 @@ describe("Room", function() {
describe("hasMembershipState", function() {
it("should return true for a matching userId and membership",
function() {
room.currentState.members = {
"@alice:bar": { userId: "@alice:bar", membership: "join" },
"@bob:bar": { userId: "@bob:bar", membership: "invite" },
};
room.currentState.getMember.mockImplementation(function(userId) {
return {
"@alice:bar": { userId: "@alice:bar", membership: "join" },
"@bob:bar": { userId: "@bob:bar", membership: "invite" },
}[userId];
});
expect(room.hasMembershipState("@bob:bar", "invite")).toBe(true);
});
it("should return false if match membership but no match userId",
function() {
room.currentState.members = {
"@alice:bar": { userId: "@alice:bar", membership: "join" },
};
room.currentState.getMember.mockImplementation(function(userId) {
return {
"@alice:bar": { userId: "@alice:bar", membership: "join" },
}[userId];
});
expect(room.hasMembershipState("@bob:bar", "join")).toBe(false);
});
it("should return false if match userId but no match membership",
function() {
room.currentState.members = {
"@alice:bar": { userId: "@alice:bar", membership: "join" },
};
room.currentState.getMember.mockImplementation(function(userId) {
return {
"@alice:bar": { userId: "@alice:bar", membership: "join" },
}[userId];
});
expect(room.hasMembershipState("@alice:bar", "ban")).toBe(false);
});
it("should return false if no match membership or userId",
function() {
room.currentState.members = {
"@alice:bar": { userId: "@alice:bar", membership: "join" },
};
room.currentState.getMember.mockImplementation(function(userId) {
return {
"@alice:bar": { userId: "@alice:bar", membership: "join" },
}[userId];
});
expect(room.hasMembershipState("@bob:bar", "invite")).toBe(false);
});
it("should return false if no members exist",
function() {
room.currentState.members = {};
expect(room.hasMembershipState("@foo:bar", "join")).toBe(false);
});
});
describe("recalculate", function() {
let stateLookup = {
// event.type + "$" event.state_key : MatrixEvent
};
const setJoinRule = function(rule) {
stateLookup["m.room.join_rules$"] = utils.mkEvent({
room.addLiveEvents([utils.mkEvent({
type: "m.room.join_rules", room: roomId, user: userA, content: {
join_rule: rule,
}, event: true,
});
})]);
};
const setAliases = function(aliases, stateKey) {
if (!stateKey) {
stateKey = "flibble";
}
stateLookup["m.room.aliases$" + stateKey] = utils.mkEvent({
type: "m.room.aliases", room: roomId, skey: stateKey, content: {
aliases: aliases,
const setAltAliases = function(aliases) {
room.addLiveEvents([utils.mkEvent({
type: "m.room.canonical_alias", room: roomId, skey: "", content: {
alt_aliases: aliases,
}, event: true,
});
})]);
};
const setRoomName = function(name) {
stateLookup["m.room.name$"] = utils.mkEvent({
room.addLiveEvents([utils.mkEvent({
type: "m.room.name", room: roomId, user: userA, content: {
name: name,
}, event: true,
});
})]);
};
const addMember = function(userId, state, opts) {
if (!state) {
@@ -644,56 +641,14 @@ describe("Room", function() {
opts.user = opts.user || userId;
opts.skey = userId;
opts.event = true;
stateLookup["m.room.member$" + userId] = utils.mkMembership(opts);
const event = utils.mkMembership(opts);
room.addLiveEvents([event]);
return event;
};
beforeEach(function() {
stateLookup = {};
room.currentState.getStateEvents.andCall(function(type, key) {
if (key === undefined) {
const prefix = type + "$";
const list = [];
for (const stateBlob in stateLookup) {
if (!stateLookup.hasOwnProperty(stateBlob)) {
continue;
}
if (stateBlob.indexOf(prefix) === 0) {
list.push(stateLookup[stateBlob]);
}
}
return list;
} else {
return stateLookup[type + "$" + key];
}
});
room.currentState.getMembers.andCall(function() {
const memberEvents = room.currentState.getStateEvents("m.room.member");
const members = [];
for (let i = 0; i < memberEvents.length; i++) {
members.push({
name: memberEvents[i].event.content &&
memberEvents[i].event.content.displayname ?
memberEvents[i].event.content.displayname :
memberEvents[i].getStateKey(),
userId: memberEvents[i].getStateKey(),
events: { member: memberEvents[i] },
});
}
return members;
});
room.currentState.getMember.andCall(function(userId) {
const memberEvent = room.currentState.getStateEvents(
"m.room.member", userId,
);
return {
name: memberEvent.event.content &&
memberEvent.event.content.displayname ?
memberEvent.event.content.displayname :
memberEvent.getStateKey(),
userId: memberEvent.getStateKey(),
events: { member: memberEvent },
};
});
// no mocking
room = new Room(roomId, null, userA);
});
describe("Room.recalculate => Stripped State Events", function() {
@@ -701,8 +656,8 @@ describe("Room", function() {
"room is an invite room", function() {
const roomName = "flibble";
addMember(userA, "invite");
stateLookup["m.room.member$" + userA].event.invite_room_state = [
const event = addMember(userA, "invite");
event.event.invite_room_state = [
{
type: "m.room.name",
state_key: "",
@@ -712,30 +667,108 @@ describe("Room", function() {
},
];
room.recalculate(userA);
expect(room.currentState.setStateEvents).toHaveBeenCalled();
// first call, first arg (which is an array), first element in array
const fakeEvent = room.currentState.setStateEvents.calls[0].
arguments[0][0];
expect(fakeEvent.getContent()).toEqual({
name: roomName,
});
room.recalculate();
expect(room.name).toEqual(roomName);
});
it("should not clobber state events if it isn't an invite room", function() {
addMember(userA, "join");
stateLookup["m.room.member$" + userA].event.invite_room_state = [
const event = addMember(userA, "join");
const roomName = "flibble";
setRoomName(roomName);
const roomNameToIgnore = "ignoreme";
event.event.invite_room_state = [
{
type: "m.room.name",
state_key: "",
content: {
name: "flibble",
name: roomNameToIgnore,
},
},
];
room.recalculate(userA);
expect(room.currentState.setStateEvents).toNotHaveBeenCalled();
room.recalculate();
expect(room.name).toEqual(roomName);
});
});
describe("Room.recalculate => Room Name using room summary", function() {
it("should use room heroes if available", function() {
addMember(userA, "invite");
addMember(userB);
addMember(userC);
addMember(userD);
room.setSummary({
"m.heroes": [userB, userC, userD],
});
room.recalculate();
expect(room.name).toEqual(`${userB} and 2 others`);
});
it("missing hero member state reverts to mxid", function() {
room.setSummary({
"m.heroes": [userB],
"m.joined_member_count": 2,
});
room.recalculate();
expect(room.name).toEqual(userB);
});
it("uses hero name from state", function() {
const name = "Mr B";
addMember(userA, "invite");
addMember(userB, "join", {name});
room.setSummary({
"m.heroes": [userB],
});
room.recalculate();
expect(room.name).toEqual(name);
});
it("uses counts from summary", function() {
const name = "Mr B";
addMember(userB, "join", {name});
room.setSummary({
"m.heroes": [userB],
"m.joined_member_count": 50,
"m.invited_member_count": 50,
});
room.recalculate();
expect(room.name).toEqual(`${name} and 98 others`);
});
it("relies on heroes in case of absent counts", function() {
const nameB = "Mr Bean";
const nameC = "Mel C";
addMember(userB, "join", {name: nameB});
addMember(userC, "join", {name: nameC});
room.setSummary({
"m.heroes": [userB, userC],
});
room.recalculate();
expect(room.name).toEqual(`${nameB} and ${nameC}`);
});
it("uses only heroes", function() {
const nameB = "Mr Bean";
addMember(userB, "join", {name: nameB});
addMember(userC, "join");
room.setSummary({
"m.heroes": [userB],
});
room.recalculate();
expect(room.name).toEqual(nameB);
});
it("reverts to empty room in case of self chat", function() {
room.setSummary({
"m.heroes": [],
"m.invited_member_count": 1,
});
room.recalculate();
expect(room.name).toEqual("Empty room");
});
});
@@ -748,7 +781,7 @@ describe("Room", function() {
addMember(userB);
addMember(userC);
addMember(userD);
room.recalculate(userA);
room.recalculate();
const name = room.name;
// we expect at least 1 member to be mentioned
const others = [userB, userC, userD];
@@ -769,10 +802,10 @@ describe("Room", function() {
addMember(userA);
addMember(userB);
addMember(userC);
room.recalculate(userA);
room.recalculate();
const name = room.name;
expect(name.indexOf(userB)).toNotEqual(-1, name);
expect(name.indexOf(userC)).toNotEqual(-1, name);
expect(name.indexOf(userB)).not.toEqual(-1, name);
expect(name.indexOf(userC)).not.toEqual(-1, name);
});
it("should return the names of members in a public (public join_rules)" +
@@ -782,10 +815,10 @@ describe("Room", function() {
addMember(userA);
addMember(userB);
addMember(userC);
room.recalculate(userA);
room.recalculate();
const name = room.name;
expect(name.indexOf(userB)).toNotEqual(-1, name);
expect(name.indexOf(userC)).toNotEqual(-1, name);
expect(name.indexOf(userB)).not.toEqual(-1, name);
expect(name.indexOf(userC)).not.toEqual(-1, name);
});
it("should show the other user's name for public (public join_rules)" +
@@ -794,9 +827,9 @@ describe("Room", function() {
setJoinRule("public");
addMember(userA);
addMember(userB);
room.recalculate(userA);
room.recalculate();
const name = room.name;
expect(name.indexOf(userB)).toNotEqual(-1, name);
expect(name.indexOf(userB)).not.toEqual(-1, name);
});
it("should show the other user's name for private " +
@@ -805,9 +838,9 @@ describe("Room", function() {
setJoinRule("invite");
addMember(userA);
addMember(userB);
room.recalculate(userA);
room.recalculate();
const name = room.name;
expect(name.indexOf(userB)).toNotEqual(-1, name);
expect(name.indexOf(userB)).not.toEqual(-1, name);
});
it("should show the other user's name for private" +
@@ -815,17 +848,17 @@ describe("Room", function() {
setJoinRule("invite");
addMember(userA, "invite", {user: userB});
addMember(userB);
room.recalculate(userA);
room.recalculate();
const name = room.name;
expect(name.indexOf(userB)).toNotEqual(-1, name);
expect(name.indexOf(userB)).not.toEqual(-1, name);
});
it("should show the room alias if one exists for private " +
"(invite join_rules) rooms if a room name doesn't exist.", function() {
const alias = "#room_alias:here";
setJoinRule("invite");
setAliases([alias, "#another:one"]);
room.recalculate(userA);
setAltAliases([alias, "#another:here"]);
room.recalculate();
const name = room.name;
expect(name).toEqual(alias);
});
@@ -834,8 +867,8 @@ describe("Room", function() {
"(public join_rules) rooms if a room name doesn't exist.", function() {
const alias = "#room_alias:here";
setJoinRule("public");
setAliases([alias, "#another:one"]);
room.recalculate(userA);
setAltAliases([alias, "#another:here"]);
room.recalculate();
const name = room.name;
expect(name).toEqual(alias);
});
@@ -845,7 +878,7 @@ describe("Room", function() {
const roomName = "A mighty name indeed";
setJoinRule("invite");
setRoomName(roomName);
room.recalculate(userA);
room.recalculate();
const name = room.name;
expect(name).toEqual(roomName);
});
@@ -855,25 +888,23 @@ describe("Room", function() {
const roomName = "A mighty name indeed";
setJoinRule("public");
setRoomName(roomName);
room.recalculate(userA);
const name = room.name;
expect(name).toEqual(roomName);
room.recalculate();
expect(room.name).toEqual(roomName);
});
it("should return 'Empty room' for private (invite join_rules) rooms if" +
" a room name and alias don't exist and it is a self-chat.", function() {
setJoinRule("invite");
addMember(userA);
room.recalculate(userA);
const name = room.name;
expect(name).toEqual("Empty room");
room.recalculate();
expect(room.name).toEqual("Empty room");
});
it("should return 'Empty room' for public (public join_rules) rooms if a" +
" room name and alias don't exist and it is a self-chat.", function() {
setJoinRule("public");
addMember(userA);
room.recalculate(userA);
room.recalculate();
const name = room.name;
expect(name).toEqual("Empty room");
});
@@ -881,7 +912,7 @@ describe("Room", function() {
it("should return 'Empty room' if there is no name, " +
"alias or members in the room.",
function() {
room.recalculate(userA);
room.recalculate();
const name = room.name;
expect(name).toEqual("Empty room");
});
@@ -890,9 +921,9 @@ describe("Room", function() {
"available",
function() {
setJoinRule("invite");
addMember(userA, 'join', {name: "Alice"});
addMember(userB, "invite", {user: userA});
room.recalculate(userB);
addMember(userB, 'join', {name: "Alice"});
addMember(userA, "invite", {user: userA});
room.recalculate();
const name = room.name;
expect(name).toEqual("Alice");
});
@@ -900,11 +931,11 @@ describe("Room", function() {
it("should return inviter mxid if display name not available",
function() {
setJoinRule("invite");
addMember(userA);
addMember(userB, "invite", {user: userA});
room.recalculate(userB);
addMember(userB);
addMember(userA, "invite", {user: userA});
room.recalculate();
const name = room.name;
expect(name).toEqual(userA);
expect(name).toEqual(userB);
});
});
});
@@ -963,7 +994,7 @@ describe("Room", function() {
it("should emit an event when a receipt is added",
function() {
const listener = expect.createSpy();
const listener = jest.fn();
room.on("Room.receipt", listener);
const ts = 13787898424;
@@ -1134,7 +1165,7 @@ describe("Room", function() {
it("should emit Room.tags event when new tags are " +
"received on the event stream",
function() {
const listener = expect.createSpy();
const listener = jest.fn();
room.on("Room.tags", listener);
const tags = { "m.foo": { "order": 0.5 } };
@@ -1151,7 +1182,7 @@ describe("Room", function() {
describe("addPendingEvent", function() {
it("should add pending events to the pendingEventList if " +
"pendingEventOrdering == 'detached'", function() {
const room = new Room(roomId, {
const room = new Room(roomId, null, userA, {
pendingEventOrdering: "detached",
});
const eventA = utils.mkMessage({
@@ -1177,7 +1208,7 @@ describe("Room", function() {
it("should add pending events to the timeline if " +
"pendingEventOrdering == 'chronological'", function() {
room = new Room(roomId, {
room = new Room(roomId, null, userA, {
pendingEventOrdering: "chronological",
});
const eventA = utils.mkMessage({
@@ -1201,7 +1232,7 @@ describe("Room", function() {
describe("updatePendingEvent", function() {
it("should remove cancelled events from the pending list", function() {
const room = new Room(roomId, {
const room = new Room(roomId, null, userA, {
pendingEventOrdering: "detached",
});
const eventA = utils.mkMessage({
@@ -1237,7 +1268,7 @@ describe("Room", function() {
it("should remove cancelled events from the timeline", function() {
const room = new Room(roomId);
const room = new Room(roomId, null, userA);
const eventA = utils.mkMessage({
room: roomId, user: userA, event: true,
});
@@ -1269,4 +1300,156 @@ describe("Room", function() {
expect(callCount).toEqual(1);
});
});
describe("loadMembersIfNeeded", function() {
function createClientMock(serverResponse, storageResponse = null) {
return {
getEventMapper: function() {
// events should already be MatrixEvents
return function(event) {return event;};
},
isCryptoEnabled() {
return true;
},
isRoomEncrypted: function() {
return false;
},
_http: {
serverResponse,
authedRequest: function() {
if (this.serverResponse instanceof Error) {
return Promise.reject(this.serverResponse);
} else {
return Promise.resolve({chunk: this.serverResponse});
}
},
},
store: {
storageResponse,
storedMembers: null,
getOutOfBandMembers: function() {
if (this.storageResponse instanceof Error) {
return Promise.reject(this.storageResponse);
} else {
return Promise.resolve(this.storageResponse);
}
},
setOutOfBandMembers: function(roomId, memberEvents) {
this.storedMembers = memberEvents;
return Promise.resolve();
},
getSyncToken: () => "sync_token",
},
};
}
const memberEvent = utils.mkMembership({
user: "@user_a:bar", mship: "join",
room: roomId, event: true, name: "User A",
});
it("should load members from server on first call", async function() {
const client = createClientMock([memberEvent]);
const room = new Room(roomId, client, null, {lazyLoadMembers: true});
await room.loadMembersIfNeeded();
const memberA = room.getMember("@user_a:bar");
expect(memberA.name).toEqual("User A");
const storedMembers = client.store.storedMembers;
expect(storedMembers.length).toEqual(1);
expect(storedMembers[0].event_id).toEqual(memberEvent.getId());
});
it("should take members from storage if available", async function() {
const memberEvent2 = utils.mkMembership({
user: "@user_a:bar", mship: "join",
room: roomId, event: true, name: "Ms A",
});
const client = createClientMock([memberEvent2], [memberEvent]);
const room = new Room(roomId, client, null, {lazyLoadMembers: true});
await room.loadMembersIfNeeded();
const memberA = room.getMember("@user_a:bar");
expect(memberA.name).toEqual("User A");
});
it("should allow retry on error", async function() {
const client = createClientMock(new Error("server says no"));
const room = new Room(roomId, client, null, {lazyLoadMembers: true});
let hasThrown = false;
try {
await room.loadMembersIfNeeded();
} catch(err) {
hasThrown = true;
}
expect(hasThrown).toEqual(true);
client._http.serverResponse = [memberEvent];
await room.loadMembersIfNeeded();
const memberA = room.getMember("@user_a:bar");
expect(memberA.name).toEqual("User A");
});
});
describe("getMyMembership", function() {
it("should return synced membership if membership isn't available yet",
function() {
const room = new Room(roomId, null, userA);
room.updateMyMembership("invite");
expect(room.getMyMembership()).toEqual("invite");
});
it("should emit a Room.myMembership event on a change",
function() {
const room = new Room(roomId, null, userA);
const events = [];
room.on("Room.myMembership", (_room, membership, oldMembership) => {
events.push({membership, oldMembership});
});
room.updateMyMembership("invite");
expect(room.getMyMembership()).toEqual("invite");
expect(events[0]).toEqual({membership: "invite", oldMembership: null});
events.splice(0); //clear
room.updateMyMembership("invite");
expect(events.length).toEqual(0);
room.updateMyMembership("join");
expect(room.getMyMembership()).toEqual("join");
expect(events[0]).toEqual({membership: "join", oldMembership: "invite"});
});
});
describe("guessDMUserId", function() {
it("should return first hero id",
function() {
const room = new Room(roomId, null, userA);
room.setSummary({'m.heroes': [userB]});
expect(room.guessDMUserId()).toEqual(userB);
});
it("should return first member that isn't self",
function() {
const room = new Room(roomId, null, userA);
room.addLiveEvents([utils.mkMembership({
user: userB, mship: "join",
room: roomId, event: true,
})]);
expect(room.guessDMUserId()).toEqual(userB);
});
it("should return self if only member present",
function() {
const room = new Room(roomId, null, userA);
expect(room.guessDMUserId()).toEqual(userA);
});
});
describe("maySendMessage", function() {
it("should return false if synced membership not join",
function() {
const room = new Room(roomId, null, userA);
room.updateMyMembership("invite");
expect(room.maySendMessage()).toEqual(false);
room.updateMyMembership("leave");
expect(room.maySendMessage()).toEqual(false);
room.updateMyMembership("join");
expect(room.maySendMessage()).toEqual(true);
});
});
});
+59 -57
View File
@@ -1,22 +1,18 @@
// This file had a function whose name is all caps, which displeases eslint
/* eslint new-cap: "off" */
import 'source-map-support/register';
import Promise from 'bluebird';
const sdk = require("../..");
const MatrixScheduler = sdk.MatrixScheduler;
const MatrixError = sdk.MatrixError;
const utils = require("../test-utils");
import {defer} from '../../src/utils';
import {MatrixError} from "../../src/http-api";
import {MatrixScheduler} from "../../src/scheduler";
import * as utils from "../test-utils";
import expect from 'expect';
import lolex from 'lolex';
jest.useFakeTimers();
describe("MatrixScheduler", function() {
let clock;
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,
@@ -26,8 +22,6 @@ describe("MatrixScheduler", function() {
});
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this
clock = lolex.install();
scheduler = new MatrixScheduler(function(ev, attempts, err) {
if (retryFn) {
return retryFn(ev, attempts, err);
@@ -41,46 +35,44 @@ describe("MatrixScheduler", function() {
});
retryFn = null;
queueFn = null;
defer = Promise.defer();
deferred = defer();
});
afterEach(function() {
clock.uninstall();
});
it("should process events in a queue in a FIFO manner", function(done) {
it("should process events in a queue in a FIFO manner", async function() {
retryFn = function() {
return 0;
};
queueFn = function() {
return "one_big_queue";
};
const deferA = Promise.defer();
const deferB = Promise.defer();
let resolvedA = false;
const deferA = defer();
const deferB = defer();
let yieldedA = false;
scheduler.setProcessFunction(function(event) {
if (resolvedA) {
if (yieldedA) {
expect(event).toEqual(eventB);
return deferB.promise;
} else {
yieldedA = true;
expect(event).toEqual(eventA);
return deferA.promise;
}
});
scheduler.queueEvent(eventA);
scheduler.queueEvent(eventB).done(function() {
expect(resolvedA).toBe(true);
done();
});
deferA.resolve({});
resolvedA = true;
deferB.resolve({});
const abPromise = Promise.all([
scheduler.queueEvent(eventA),
scheduler.queueEvent(eventB),
]);
deferB.resolve({b: true});
deferA.resolve({a: true});
const [a, b] = await abPromise;
expect(a.a).toEqual(true);
expect(b.b).toEqual(true);
});
it("should invoke the retryFn on failure and wait the amount of time specified",
function(done) {
async function() {
const waitTimeMs = 1500;
const retryDefer = Promise.defer();
const retryDefer = defer();
retryFn = function() {
retryDefer.resolve();
return waitTimeMs;
@@ -94,27 +86,29 @@ 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
return Promise.defer().promise;
// don't care about this deferred
return new Promise();
}
expect(procCount).toBeLessThan(3);
});
scheduler.queueEvent(eventA);
// as queueing doesn't start processing synchronously anymore (see commit bbdb5ac)
// wait just long enough before it does
await Promise.resolve();
expect(procCount).toEqual(1);
defer.reject({});
retryDefer.promise.done(function() {
expect(procCount).toEqual(1);
clock.tick(waitTimeMs);
expect(procCount).toEqual(2);
done();
});
deferred.reject({});
await retryDefer.promise;
expect(procCount).toEqual(1);
jest.advanceTimersByTime(waitTimeMs);
await Promise.resolve();
expect(procCount).toEqual(2);
});
it("should give up if the retryFn on failure returns -1 and try the next event",
function(done) {
async function() {
// Queue A & B.
// Reject A and return -1 on retry.
// Expect B to be tried next and the promise for A to be rejected.
@@ -122,11 +116,11 @@ describe("MatrixScheduler", function() {
return -1;
};
queueFn = function() {
return "yep";
};
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;
@@ -142,13 +136,17 @@ describe("MatrixScheduler", function() {
const globalA = scheduler.queueEvent(eventA);
scheduler.queueEvent(eventB);
// as queueing doesn't start processing synchronously anymore (see commit bbdb5ac)
// wait just long enough before it does
await Promise.resolve();
expect(procCount).toEqual(1);
deferA.reject({});
globalA.catch(function() {
try {
await globalA;
} catch(err) {
await Promise.resolve();
expect(procCount).toEqual(2);
done();
});
}
});
it("should treat each queue separately", function(done) {
@@ -177,14 +175,14 @@ describe("MatrixScheduler", function() {
const expectOrder = [
eventA.getId(), eventB.getId(), eventD.getId(),
];
const deferA = Promise.defer();
const deferA = defer();
scheduler.setProcessFunction(function(event) {
const id = expectOrder.shift();
expect(id).toEqual(event.getId());
if (expectOrder.length === 0) {
done();
}
return id === eventA.getId() ? deferA.promise : defer.promise;
return id === eventA.getId() ? deferA.promise : deferred.promise;
});
scheduler.queueEvent(eventA);
scheduler.queueEvent(eventB);
@@ -195,7 +193,7 @@ describe("MatrixScheduler", function() {
setTimeout(function() {
deferA.resolve({});
}, 1000);
clock.tick(1000);
jest.advanceTimersByTime(1000);
});
describe("queueEvent", function() {
@@ -298,9 +296,13 @@ 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
Promise.resolve().then(() => {
expect(procCount).toEqual(1);
});
expect(procCount).toEqual(1);
});
it("should not call the processFn if there are no queued events", function() {
@@ -310,7 +312,7 @@ describe("MatrixScheduler", function() {
let procCount = 0;
scheduler.setProcessFunction(function(ev) {
procCount += 1;
return defer.promise;
return deferred.promise;
});
expect(procCount).toEqual(0);
});
+59 -8
View File
@@ -1,5 +1,6 @@
/*
Copyright 2017 Vector Creations Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,19 +15,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
"use strict";
import 'source-map-support/register';
import utils from "../test-utils";
import sdk from "../..";
import expect from 'expect';
const SyncAccumulator = sdk.SyncAccumulator;
import {SyncAccumulator} from "../../src/sync-accumulator";
describe("SyncAccumulator", function() {
let sa;
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this
sa = new SyncAccumulator({
maxTimelineEntries: 10,
});
@@ -52,6 +46,11 @@ describe("SyncAccumulator", function() {
member("bob", "join"),
],
},
summary: {
"m.heroes": undefined,
"m.joined_member_count": undefined,
"m.invited_member_count": undefined,
},
timeline: {
events: [msg("alice", "hi")],
prev_batch: "something",
@@ -318,6 +317,58 @@ describe("SyncAccumulator", function() {
},
});
});
describe("summary field", function() {
function createSyncResponseWithSummary(summary) {
return {
next_batch: "abc",
rooms: {
invite: {},
leave: {},
join: {
"!foo:bar": {
account_data: { events: [] },
ephemeral: { events: [] },
unread_notifications: {},
state: {
events: [],
},
summary: summary,
timeline: {
events: [],
prev_batch: "something",
},
},
},
},
};
}
it("should copy summary properties", function() {
sa.accumulate(createSyncResponseWithSummary({
"m.heroes": ["@alice:bar"],
"m.invited_member_count": 2,
}));
const summary = sa.getJSON().roomsData.join["!foo:bar"].summary;
expect(summary["m.invited_member_count"]).toEqual(2);
expect(summary["m.heroes"]).toEqual(["@alice:bar"]);
});
it("should accumulate summary properties", function() {
sa.accumulate(createSyncResponseWithSummary({
"m.heroes": ["@alice:bar"],
"m.invited_member_count": 2,
}));
sa.accumulate(createSyncResponseWithSummary({
"m.heroes": ["@bob:bar"],
"m.joined_member_count": 5,
}));
const summary = sa.getJSON().roomsData.join["!foo:bar"].summary;
expect(summary["m.invited_member_count"]).toEqual(2);
expect(summary["m.joined_member_count"]).toEqual(5);
expect(summary["m.heroes"]).toEqual(["@bob:bar"]);
});
});
});
function syncSkeleton(joinObj) {
+33 -51
View File
@@ -1,13 +1,6 @@
"use strict";
import 'source-map-support/register';
import Promise from 'bluebird';
const sdk = require("../..");
const EventTimeline = sdk.EventTimeline;
const TimelineWindow = sdk.TimelineWindow;
const TimelineIndex = require("../../lib/timeline-window").TimelineIndex;
const utils = require("../test-utils");
import expect from 'expect';
import {EventTimeline} from "../../src/models/event-timeline";
import {TimelineIndex, TimelineWindow} from "../../src/timeline-window";
import * as utils from "../test-utils";
const ROOM_ID = "roomId";
const USER_ID = "userId";
@@ -67,10 +60,6 @@ function createLinkedTimelines() {
describe("TimelineIndex", function() {
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this
});
describe("minIndex", function() {
it("should return the min index relative to BaseIndex", function() {
const timelineIndex = new TimelineIndex(createTimeline(), 0);
@@ -153,7 +142,7 @@ describe("TimelineWindow", function() {
let timelineSet;
let client;
function createWindow(timeline, opts) {
timelineSet = {};
timelineSet = {getTimelineForEvent: () => null};
client = {};
client.getEventTimeline = function(timelineSet0, eventId0) {
expect(timelineSet0).toBe(timelineSet);
@@ -163,12 +152,8 @@ describe("TimelineWindow", function() {
return new TimelineWindow(client, timelineSet, opts);
}
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this
});
describe("load", function() {
it("should initialise from the live timeline", function(done) {
it("should initialise from the live timeline", function() {
const liveTimeline = createTimeline();
const room = {};
room.getLiveTimeline = function() {
@@ -176,17 +161,17 @@ describe("TimelineWindow", function() {
};
const timelineWindow = new TimelineWindow(undefined, room);
timelineWindow.load(undefined, 2).then(function() {
return timelineWindow.load(undefined, 2).then(function() {
const expectedEvents = liveTimeline.getEvents().slice(1);
expect(timelineWindow.getEvents()).toEqual(expectedEvents);
}).nodeify(done);
});
});
it("should initialise from a specific event", function(done) {
it("should initialise from a specific event", function() {
const timeline = createTimeline();
const eventId = timeline.getEvents()[1].getId();
const timelineSet = {};
const timelineSet = {getTimelineForEvent: () => null};
const client = {};
client.getEventTimeline = function(timelineSet0, eventId0) {
expect(timelineSet0).toBe(timelineSet);
@@ -195,21 +180,20 @@ describe("TimelineWindow", function() {
};
const timelineWindow = new TimelineWindow(client, timelineSet);
timelineWindow.load(eventId, 3).then(function() {
return timelineWindow.load(eventId, 3).then(function() {
const expectedEvents = timeline.getEvents();
expect(timelineWindow.getEvents()).toEqual(expectedEvents);
}).nodeify(done);
});
});
it("canPaginate should return false until load has returned",
function(done) {
it("canPaginate should return false until load has returned", function() {
const timeline = createTimeline();
timeline.setPaginationToken("toktok1", EventTimeline.BACKWARDS);
timeline.setPaginationToken("toktok2", EventTimeline.FORWARDS);
const eventId = timeline.getEvents()[1].getId();
const timelineSet = {};
const timelineSet = {getTimelineForEvent: () => null};
const client = {};
const timelineWindow = new TimelineWindow(client, timelineSet);
@@ -222,25 +206,24 @@ describe("TimelineWindow", function() {
return Promise.resolve(timeline);
};
timelineWindow.load(eventId, 3).then(function() {
return timelineWindow.load(eventId, 3).then(function() {
const expectedEvents = timeline.getEvents();
expect(timelineWindow.getEvents()).toEqual(expectedEvents);
expect(timelineWindow.canPaginate(EventTimeline.BACKWARDS))
.toBe(true);
expect(timelineWindow.canPaginate(EventTimeline.FORWARDS))
.toBe(true);
}).nodeify(done);
});
});
});
describe("pagination", function() {
it("should be able to advance across the initial timeline",
function(done) {
it("should be able to advance across the initial timeline", function() {
const timeline = createTimeline();
const eventId = timeline.getEvents()[1].getId();
const timelineWindow = createWindow(timeline);
timelineWindow.load(eventId, 1).then(function() {
return timelineWindow.load(eventId, 1).then(function() {
const expectedEvents = [timeline.getEvents()[1]];
expect(timelineWindow.getEvents()).toEqual(expectedEvents);
@@ -277,15 +260,15 @@ describe("TimelineWindow", function() {
return timelineWindow.paginate(EventTimeline.BACKWARDS, 2);
}).then(function(success) {
expect(success).toBe(false);
}).nodeify(done);
});
});
it("should advance into next timeline", function(done) {
it("should advance into next timeline", function() {
const tls = createLinkedTimelines();
const eventId = tls[0].getEvents()[1].getId();
const timelineWindow = createWindow(tls[0], {windowLimit: 5});
timelineWindow.load(eventId, 3).then(function() {
return timelineWindow.load(eventId, 3).then(function() {
const expectedEvents = tls[0].getEvents();
expect(timelineWindow.getEvents()).toEqual(expectedEvents);
@@ -322,15 +305,15 @@ describe("TimelineWindow", function() {
return timelineWindow.paginate(EventTimeline.FORWARDS, 2);
}).then(function(success) {
expect(success).toBe(false);
}).nodeify(done);
});
});
it("should retreat into previous timeline", function(done) {
it("should retreat into previous timeline", function() {
const tls = createLinkedTimelines();
const eventId = tls[1].getEvents()[1].getId();
const timelineWindow = createWindow(tls[1], {windowLimit: 5});
timelineWindow.load(eventId, 3).then(function() {
return timelineWindow.load(eventId, 3).then(function() {
const expectedEvents = tls[1].getEvents();
expect(timelineWindow.getEvents()).toEqual(expectedEvents);
@@ -367,10 +350,10 @@ describe("TimelineWindow", function() {
return timelineWindow.paginate(EventTimeline.BACKWARDS, 2);
}).then(function(success) {
expect(success).toBe(false);
}).nodeify(done);
});
});
it("should make forward pagination requests", function(done) {
it("should make forward pagination requests", function() {
const timeline = createTimeline();
timeline.setPaginationToken("toktok", EventTimeline.FORWARDS);
@@ -386,7 +369,7 @@ describe("TimelineWindow", function() {
return Promise.resolve(true);
};
timelineWindow.load(eventId, 3).then(function() {
return timelineWindow.load(eventId, 3).then(function() {
const expectedEvents = timeline.getEvents();
expect(timelineWindow.getEvents()).toEqual(expectedEvents);
@@ -399,11 +382,11 @@ describe("TimelineWindow", function() {
expect(success).toBe(true);
const expectedEvents = timeline.getEvents().slice(0, 5);
expect(timelineWindow.getEvents()).toEqual(expectedEvents);
}).nodeify(done);
});
});
it("should make backward pagination requests", function(done) {
it("should make backward pagination requests", function() {
const timeline = createTimeline();
timeline.setPaginationToken("toktok", EventTimeline.BACKWARDS);
@@ -419,7 +402,7 @@ describe("TimelineWindow", function() {
return Promise.resolve(true);
};
timelineWindow.load(eventId, 3).then(function() {
return timelineWindow.load(eventId, 3).then(function() {
const expectedEvents = timeline.getEvents();
expect(timelineWindow.getEvents()).toEqual(expectedEvents);
@@ -432,11 +415,10 @@ describe("TimelineWindow", function() {
expect(success).toBe(true);
const expectedEvents = timeline.getEvents().slice(1, 6);
expect(timelineWindow.getEvents()).toEqual(expectedEvents);
}).nodeify(done);
});
});
it("should limit the number of unsuccessful pagination requests",
function(done) {
it("should limit the number of unsuccessful pagination requests", function() {
const timeline = createTimeline();
timeline.setPaginationToken("toktok", EventTimeline.FORWARDS);
@@ -452,7 +434,7 @@ describe("TimelineWindow", function() {
return Promise.resolve(true);
};
timelineWindow.load(eventId, 3).then(function() {
return timelineWindow.load(eventId, 3).then(function() {
const expectedEvents = timeline.getEvents();
expect(timelineWindow.getEvents()).toEqual(expectedEvents);
@@ -471,7 +453,7 @@ describe("TimelineWindow", function() {
.toBe(false);
expect(timelineWindow.canPaginate(EventTimeline.FORWARDS))
.toBe(true);
}).nodeify(done);
});
});
});
});
+2 -8
View File
@@ -1,17 +1,11 @@
"use strict";
import 'source-map-support/register';
const sdk = require("../..");
const User = sdk.User;
const utils = require("../test-utils");
import expect from 'expect';
import {User} from "../../src/models/user";
import * as utils from "../test-utils";
describe("User", function() {
const userId = "@alice:bar";
let user;
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this
user = new User(userId);
});
+3 -12
View File
@@ -1,15 +1,6 @@
"use strict";
import 'source-map-support/register';
const utils = require("../../lib/utils");
const testUtils = require("../test-utils");
import expect from 'expect';
import * as utils from "../../src/utils";
describe("utils", function() {
beforeEach(function() {
testUtils.beforeEach(this); // eslint-disable-line no-invalid-this
});
describe("encodeParams", function() {
it("should url encode and concat with &s", function() {
const params = {
@@ -135,7 +126,7 @@ describe("utils", function() {
utils.checkObjectHasKeys({
foo: "bar",
}, ["foo"]);
}).toNotThrow();
}).not.toThrow();
});
});
@@ -152,7 +143,7 @@ describe("utils", function() {
utils.checkObjectHasNoAdditionalKeys({
foo: "bar",
}, ["foo"]);
}).toNotThrow();
}).not.toThrow();
});
});
+25
View File
@@ -0,0 +1,25 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
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.
*/
export {};
declare global {
namespace NodeJS {
interface Global {
localStorage: Storage;
}
}
}
+8 -2
View File
@@ -20,7 +20,7 @@ limitations under the License.
* @module
*/
export default class Reemitter {
export class ReEmitter {
constructor(target) {
this.target = target;
@@ -34,12 +34,18 @@ export default class Reemitter {
}
reEmit(source, eventNames) {
// We include the source as the last argument for event handlers which may need it,
// such as read receipt listeners on the client class which won't have the context
// of the room.
const forSource = (handler, ...args) => {
handler(...args, source);
};
for (const eventName of eventNames) {
if (this.boundHandlers[eventName] === undefined) {
this.boundHandlers[eventName] = this._handleEvent.bind(this, eventName);
}
const boundHandler = this.boundHandlers[eventName];
const boundHandler = forSource.bind(this, this.boundHandlers[eventName]);
source.on(eventName, boundHandler);
}
}
+536
View File
@@ -0,0 +1,536 @@
/*
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.
*/
/** @module auto-discovery */
import {logger} from './logger';
import {URL as NodeURL} from "url";
// Dev note: Auto discovery is part of the spec.
// See: https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
/**
* Description for what an automatically discovered client configuration
* would look like. Although this is a class, it is recommended that it
* be treated as an interface definition rather than as a class.
*
* Additional properties than those defined here may be present, and
* should follow the Java package naming convention.
*/
class DiscoveredClientConfig { // eslint-disable-line no-unused-vars
// Dev note: this is basically a copy/paste of the .well-known response
// object as defined in the spec. It does have additional information,
// however. Overall, this exists to serve as a place for documentation
// and not functionality.
// See https://matrix.org/docs/spec/client_server/r0.4.0.html#get-well-known-matrix-client
constructor() {
/**
* The homeserver configuration the client should use. This will
* always be present on the object.
* @type {{state: string, base_url: string}} The configuration.
*/
this["m.homeserver"] = {
/**
* The lookup result state. If this is anything other than
* AutoDiscovery.SUCCESS then base_url may be falsey. Additionally,
* if this is not AutoDiscovery.SUCCESS then the client should
* assume the other properties in the client config (such as
* the identity server configuration) are not valid.
*/
state: AutoDiscovery.PROMPT,
/**
* If the state is AutoDiscovery.FAIL_ERROR or .FAIL_PROMPT
* then this will contain a human-readable (English) message
* for what went wrong. If the state is none of those previously
* mentioned, this will be falsey.
*/
error: "Something went wrong",
/**
* The base URL clients should use to talk to the homeserver,
* particularly for the login process. May be falsey if the
* state is not AutoDiscovery.SUCCESS.
*/
base_url: "https://matrix.org",
};
/**
* The identity server configuration the client should use. This
* will always be present on teh object.
* @type {{state: string, base_url: string}} The configuration.
*/
this["m.identity_server"] = {
/**
* The lookup result state. If this is anything other than
* AutoDiscovery.SUCCESS then base_url may be falsey.
*/
state: AutoDiscovery.PROMPT,
/**
* The base URL clients should use for interacting with the
* identity server. May be falsey if the state is not
* AutoDiscovery.SUCCESS.
*/
base_url: "https://vector.im",
};
}
}
/**
* Utilities for automatically discovery resources, such as homeservers
* for users to log in to.
*/
export class AutoDiscovery {
// Dev note: the constants defined here are related to but not
// exactly the same as those in the spec. This is to hopefully
// translate the meaning of the states in the spec, but also
// support our own if needed.
static get ERROR_INVALID() {
return "Invalid homeserver discovery response";
}
static get ERROR_GENERIC_FAILURE() {
return "Failed to get autodiscovery configuration from server";
}
static get ERROR_INVALID_HS_BASE_URL() {
return "Invalid base_url for m.homeserver";
}
static get ERROR_INVALID_HOMESERVER() {
return "Homeserver URL does not appear to be a valid Matrix homeserver";
}
static get ERROR_INVALID_IS_BASE_URL() {
return "Invalid base_url for m.identity_server";
}
static get ERROR_INVALID_IDENTITY_SERVER() {
return "Identity server URL does not appear to be a valid identity server";
}
static get ERROR_INVALID_IS() {
return "Invalid identity server discovery response";
}
static get ERROR_MISSING_WELLKNOWN() {
return "No .well-known JSON file found";
}
static get ERROR_INVALID_JSON() {
return "Invalid JSON";
}
static get ALL_ERRORS() {
return [
AutoDiscovery.ERROR_INVALID,
AutoDiscovery.ERROR_GENERIC_FAILURE,
AutoDiscovery.ERROR_INVALID_HS_BASE_URL,
AutoDiscovery.ERROR_INVALID_HOMESERVER,
AutoDiscovery.ERROR_INVALID_IS_BASE_URL,
AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER,
AutoDiscovery.ERROR_INVALID_IS,
AutoDiscovery.ERROR_MISSING_WELLKNOWN,
AutoDiscovery.ERROR_INVALID_JSON,
];
}
/**
* The auto discovery failed. The client is expected to communicate
* the error to the user and refuse logging in.
* @return {string}
* @constructor
*/
static get FAIL_ERROR() { return "FAIL_ERROR"; }
/**
* The auto discovery failed, however the client may still recover
* from the problem. The client is recommended to that the same
* action it would for PROMPT while also warning the user about
* what went wrong. The client may also treat this the same as
* a FAIL_ERROR state.
* @return {string}
* @constructor
*/
static get FAIL_PROMPT() { return "FAIL_PROMPT"; }
/**
* The auto discovery didn't fail but did not find anything of
* interest. The client is expected to prompt the user for more
* information, or fail if it prefers.
* @return {string}
* @constructor
*/
static get PROMPT() { return "PROMPT"; }
/**
* The auto discovery was successful.
* @return {string}
* @constructor
*/
static get SUCCESS() { return "SUCCESS"; }
/**
* Validates and verifies client configuration information for purposes
* of logging in. Such information includes the homeserver URL
* and identity server URL the client would want. Additional details
* may also be included, and will be transparently brought into the
* response object unaltered.
* @param {string} wellknown The configuration object itself, as returned
* by the .well-known auto-discovery endpoint.
* @return {Promise<DiscoveredClientConfig>} Resolves to the verified
* configuration, which may include error states. Rejects on unexpected
* failure, not when verification fails.
*/
static async fromDiscoveryConfig(wellknown) {
// Step 1 is to get the config, which is provided to us here.
// We default to an error state to make the first few checks easier to
// write. We'll update the properties of this object over the duration
// of this function.
const clientConfig = {
"m.homeserver": {
state: AutoDiscovery.FAIL_ERROR,
error: AutoDiscovery.ERROR_INVALID,
base_url: null,
},
"m.identity_server": {
// Technically, we don't have a problem with the identity server
// config at this point.
state: AutoDiscovery.PROMPT,
error: null,
base_url: null,
},
};
if (!wellknown || !wellknown["m.homeserver"]) {
logger.error("No m.homeserver key in config");
clientConfig["m.homeserver"].state = AutoDiscovery.FAIL_PROMPT;
clientConfig["m.homeserver"].error = AutoDiscovery.ERROR_INVALID;
return Promise.resolve(clientConfig);
}
if (!wellknown["m.homeserver"]["base_url"]) {
logger.error("No m.homeserver base_url in config");
clientConfig["m.homeserver"].state = AutoDiscovery.FAIL_PROMPT;
clientConfig["m.homeserver"].error = AutoDiscovery.ERROR_INVALID_HS_BASE_URL;
return Promise.resolve(clientConfig);
}
// Step 2: Make sure the homeserver URL is valid *looking*. We'll make
// sure it points to a homeserver in Step 3.
const hsUrl = this._sanitizeWellKnownUrl(
wellknown["m.homeserver"]["base_url"],
);
if (!hsUrl) {
logger.error("Invalid base_url for m.homeserver");
clientConfig["m.homeserver"].error = AutoDiscovery.ERROR_INVALID_HS_BASE_URL;
return Promise.resolve(clientConfig);
}
// Step 3: Make sure the homeserver URL points to a homeserver.
const hsVersions = await this._fetchWellKnownObject(
`${hsUrl}/_matrix/client/versions`,
);
if (!hsVersions || !hsVersions.raw["versions"]) {
logger.error("Invalid /versions response");
clientConfig["m.homeserver"].error = AutoDiscovery.ERROR_INVALID_HOMESERVER;
// Supply the base_url to the caller because they may be ignoring liveliness
// errors, like this one.
clientConfig["m.homeserver"].base_url = hsUrl;
return Promise.resolve(clientConfig);
}
// Step 4: Now that the homeserver looks valid, update our client config.
clientConfig["m.homeserver"] = {
state: AutoDiscovery.SUCCESS,
error: null,
base_url: hsUrl,
};
// Step 5: Try to pull out the identity server configuration
let isUrl = "";
if (wellknown["m.identity_server"]) {
// We prepare a failing identity server response to save lines later
// in this branch.
const failingClientConfig = {
"m.homeserver": clientConfig["m.homeserver"],
"m.identity_server": {
state: AutoDiscovery.FAIL_PROMPT,
error: AutoDiscovery.ERROR_INVALID_IS,
base_url: null,
},
};
// Step 5a: Make sure the URL is valid *looking*. We'll make sure it
// points to an identity server in Step 5b.
isUrl = this._sanitizeWellKnownUrl(
wellknown["m.identity_server"]["base_url"],
);
if (!isUrl) {
logger.error("Invalid base_url for m.identity_server");
failingClientConfig["m.identity_server"].error =
AutoDiscovery.ERROR_INVALID_IS_BASE_URL;
return Promise.resolve(failingClientConfig);
}
// Step 5b: Verify there is an identity server listening on the provided
// URL.
const isResponse = await this._fetchWellKnownObject(
`${isUrl}/_matrix/identity/api/v1`,
);
if (!isResponse || !isResponse.raw || isResponse.action !== "SUCCESS") {
logger.error("Invalid /api/v1 response");
failingClientConfig["m.identity_server"].error =
AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER;
// Supply the base_url to the caller because they may be ignoring
// liveliness errors, like this one.
failingClientConfig["m.identity_server"].base_url = isUrl;
return Promise.resolve(failingClientConfig);
}
}
// Step 6: Now that the identity server is valid, or never existed,
// populate the IS section.
if (isUrl && isUrl.length > 0) {
clientConfig["m.identity_server"] = {
state: AutoDiscovery.SUCCESS,
error: null,
base_url: isUrl,
};
}
// Step 7: Copy any other keys directly into the clientConfig. This is for
// things like custom configuration of services.
Object.keys(wellknown)
.map((k) => {
if (k === "m.homeserver" || k === "m.identity_server") {
// Only copy selected parts of the config to avoid overwriting
// properties computed by the validation logic above.
const notProps = ["error", "state", "base_url"];
for (const prop of Object.keys(wellknown[k])) {
if (notProps.includes(prop)) continue;
clientConfig[k][prop] = wellknown[k][prop];
}
} else {
// Just copy the whole thing over otherwise
clientConfig[k] = wellknown[k];
}
});
// Step 8: Give the config to the caller (finally)
return Promise.resolve(clientConfig);
}
/**
* Attempts to automatically discover client configuration information
* prior to logging in. Such information includes the homeserver URL
* and identity server URL the client would want. Additional details
* may also be discovered, and will be transparently included in the
* response object unaltered.
* @param {string} domain The homeserver domain to perform discovery
* on. For example, "matrix.org".
* @return {Promise<DiscoveredClientConfig>} Resolves to the discovered
* configuration, which may include error states. Rejects on unexpected
* failure, not when discovery fails.
*/
static async findClientConfig(domain) {
if (!domain || typeof(domain) !== "string" || domain.length === 0) {
throw new Error("'domain' must be a string of non-zero length");
}
// We use a .well-known lookup for all cases. According to the spec, we
// can do other discovery mechanisms if we want such as custom lookups
// however we won't bother with that here (mostly because the spec only
// supports .well-known right now).
//
// By using .well-known, we need to ensure we at least pull out a URL
// for the homeserver. We don't really need an identity server configuration
// but will return one anyways (with state PROMPT) to make development
// easier for clients. If we can't get a homeserver URL, all bets are
// off on the rest of the config and we'll assume it is invalid too.
// We default to an error state to make the first few checks easier to
// write. We'll update the properties of this object over the duration
// of this function.
const clientConfig = {
"m.homeserver": {
state: AutoDiscovery.FAIL_ERROR,
error: AutoDiscovery.ERROR_INVALID,
base_url: null,
},
"m.identity_server": {
// Technically, we don't have a problem with the identity server
// config at this point.
state: AutoDiscovery.PROMPT,
error: null,
base_url: null,
},
};
// Step 1: Actually request the .well-known JSON file and make sure it
// at least has a homeserver definition.
const wellknown = await this._fetchWellKnownObject(
`https://${domain}/.well-known/matrix/client`,
);
if (!wellknown || wellknown.action !== "SUCCESS") {
logger.error("No response or error when parsing .well-known");
if (wellknown.reason) logger.error(wellknown.reason);
if (wellknown.action === "IGNORE") {
clientConfig["m.homeserver"] = {
state: AutoDiscovery.PROMPT,
error: null,
base_url: null,
};
} else {
// this can only ever be FAIL_PROMPT at this point.
clientConfig["m.homeserver"].state = AutoDiscovery.FAIL_PROMPT;
clientConfig["m.homeserver"].error = AutoDiscovery.ERROR_INVALID;
}
return Promise.resolve(clientConfig);
}
// Step 2: Validate and parse the config
return AutoDiscovery.fromDiscoveryConfig(wellknown.raw);
}
/**
* Gets the raw discovery client configuration for the given domain name.
* Should only be used if there's no validation to be done on the resulting
* object, otherwise use findClientConfig().
* @param {string} domain The domain to get the client config for.
* @returns {Promise<object>} Resolves to the domain's client config. Can
* be an empty object.
*/
static async getRawClientConfig(domain) {
if (!domain || typeof(domain) !== "string" || domain.length === 0) {
throw new Error("'domain' must be a string of non-zero length");
}
const response = await this._fetchWellKnownObject(
`https://${domain}/.well-known/matrix/client`,
);
if (!response) return {};
return response.raw || {};
}
/**
* Sanitizes a given URL to ensure it is either an HTTP or HTTP URL and
* is suitable for the requirements laid out by .well-known auto discovery.
* If valid, the URL will also be stripped of any trailing slashes.
* @param {string} url The potentially invalid URL to sanitize.
* @return {string|boolean} The sanitized URL or a falsey value if the URL is invalid.
* @private
*/
static _sanitizeWellKnownUrl(url) {
if (!url) return false;
try {
// We have to try and parse the URL using the NodeJS URL
// library if we're on NodeJS and use the browser's URL
// library when we're in a browser. To accomplish this, we
// try the NodeJS version first and fall back to the browser.
let parsed = null;
try {
if (NodeURL) parsed = new NodeURL(url);
else parsed = new URL(url);
} catch (e) {
parsed = new URL(url);
}
if (!parsed || !parsed.hostname) return false;
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return false;
const port = parsed.port ? `:${parsed.port}` : "";
const path = parsed.pathname ? parsed.pathname : "";
let saferUrl = `${parsed.protocol}//${parsed.hostname}${port}${path}`;
if (saferUrl.endsWith("/")) {
saferUrl = saferUrl.substring(0, saferUrl.length - 1);
}
return saferUrl;
} catch (e) {
logger.error(e);
return false;
}
}
/**
* Fetches a JSON object from a given URL, as expected by all .well-known
* related lookups. If the server gives a 404 then the `action` will be
* IGNORE. If the server returns something that isn't JSON, the `action`
* will be FAIL_PROMPT. For any other failure the `action` will be FAIL_PROMPT.
*
* The returned object will be a result of the call in object form with
* the following properties:
* raw: The JSON object returned by the server.
* action: One of SUCCESS, IGNORE, or FAIL_PROMPT.
* reason: Relatively human readable description of what went wrong.
* error: The actual Error, if one exists.
* @param {string} url The URL to fetch a JSON object from.
* @return {Promise<object>} Resolves to the returned state.
* @private
*/
static async _fetchWellKnownObject(url) {
return new Promise(function(resolve, reject) {
const request = require("./matrix").getRequest();
if (!request) throw new Error("No request library available");
request(
{ method: "GET", uri: url, timeout: 5000 },
(err, response, body) => {
if (err || response &&
(response.statusCode < 200 || response.statusCode >= 300)
) {
let action = "FAIL_PROMPT";
let reason = (err ? err.message : null) || "General failure";
if (response && response.statusCode === 404) {
action = "IGNORE";
reason = AutoDiscovery.ERROR_MISSING_WELLKNOWN;
}
resolve({raw: {}, action: action, reason: reason, error: err});
return;
}
try {
resolve({raw: JSON.parse(body), action: "SUCCESS"});
} catch (e) {
let reason = AutoDiscovery.ERROR_INVALID;
if (e.name === "SyntaxError") {
reason = AutoDiscovery.ERROR_INVALID_JSON;
}
resolve({
raw: {},
action: "FAIL_PROMPT",
reason: reason,
error: e,
});
}
},
);
});
}
}
+882 -198
View File
File diff suppressed because it is too large Load Diff
+54
View File
@@ -0,0 +1,54 @@
/*
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import * as matrixcs from "./matrix";
import request from "browser-request";
import queryString from "qs";
matrixcs.request(function(opts, fn) {
// We manually fix the query string for browser-request because
// it doesn't correctly handle cases like ?via=one&via=two. Instead
// we mimic `request`'s query string interface to make it all work
// as expected.
// browser-request will happily take the constructed string as the
// query string without trying to modify it further.
opts.qs = queryString.stringify(opts.qs || {}, opts.qsStringifyOptions);
return request(opts, fn);
});
// just *accessing* indexedDB throws an exception in firefox with
// indexeddb disabled.
let indexedDB;
try {
indexedDB = global.indexedDB;
} catch(e) {}
// if our browser (appears to) support indexeddb, use an indexeddb crypto store.
if (indexedDB) {
matrixcs.setCryptoStoreFactory(
function() {
return new matrixcs.IndexedDBCryptoStore(
indexedDB, "matrix-js-sdk:crypto",
);
},
);
}
// We export 3 things to make browserify happy as well as downstream projects.
// It's awkward, but required.
export * from "./matrix";
export default matrixcs; // keep export for browserify package deps
global.matrixcs = matrixcs;
+2625 -550
View File
File diff suppressed because it is too large Load Diff
+99
View File
@@ -0,0 +1,99 @@
/*
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.
*/
/** @module ContentHelpers */
/**
* Generates the content for a HTML Message event
* @param {string} body the plaintext body of the message
* @param {string} htmlBody the HTML representation of the message
* @returns {{msgtype: string, format: string, body: string, formatted_body: string}}
*/
export function makeHtmlMessage(body, htmlBody) {
return {
msgtype: "m.text",
format: "org.matrix.custom.html",
body: body,
formatted_body: htmlBody,
};
}
/**
* Generates the content for a HTML Notice event
* @param {string} body the plaintext body of the notice
* @param {string} htmlBody the HTML representation of the notice
* @returns {{msgtype: string, format: string, body: string, formatted_body: string}}
*/
export function makeHtmlNotice(body, htmlBody) {
return {
msgtype: "m.notice",
format: "org.matrix.custom.html",
body: body,
formatted_body: htmlBody,
};
}
/**
* Generates the content for a HTML Emote event
* @param {string} body the plaintext body of the emote
* @param {string} htmlBody the HTML representation of the emote
* @returns {{msgtype: string, format: string, body: string, formatted_body: string}}
*/
export function makeHtmlEmote(body, htmlBody) {
return {
msgtype: "m.emote",
format: "org.matrix.custom.html",
body: body,
formatted_body: htmlBody,
};
}
/**
* Generates the content for a Plaintext Message event
* @param {string} body the plaintext body of the emote
* @returns {{msgtype: string, body: string}}
*/
export function makeTextMessage(body) {
return {
msgtype: "m.text",
body: body,
};
}
/**
* Generates the content for a Plaintext Notice event
* @param {string} body the plaintext body of the notice
* @returns {{msgtype: string, body: string}}
*/
export function makeNotice(body) {
return {
msgtype: "m.notice",
body: body,
};
}
/**
* Generates the content for a Plaintext Emote event
* @param {string} body the plaintext body of the emote
* @returns {{msgtype: string, body: string}}
*/
export function makeEmoteMessage(body) {
return {
msgtype: "m.emote",
body: body,
};
}
+84 -84
View File
@@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -16,94 +17,93 @@ limitations under the License.
/**
* @module content-repo
*/
const utils = require("./utils");
/** Content Repo utility functions */
module.exports = {
/**
* Get the HTTP URL for an MXC URI.
* @param {string} baseUrl The base homeserver url which has a content repo.
* @param {string} mxc The mxc:// URI.
* @param {Number} width The desired width of the thumbnail.
* @param {Number} height The desired height of the thumbnail.
* @param {string} resizeMethod The thumbnail resize method to use, either
* "crop" or "scale".
* @param {Boolean} allowDirectLinks If true, return any non-mxc URLs
* directly. Fetching such URLs will leak information about the user to
* anyone they share a room with. If false, will return the emptry string
* for such URLs.
* @return {string} The complete URL to the content.
*/
getHttpUriForMxc: function(baseUrl, mxc, width, height,
resizeMethod, allowDirectLinks) {
if (typeof mxc !== "string" || !mxc) {
import * as utils from "./utils";
/**
* Get the HTTP URL for an MXC URI.
* @param {string} baseUrl The base homeserver url which has a content repo.
* @param {string} mxc The mxc:// URI.
* @param {Number} width The desired width of the thumbnail.
* @param {Number} height The desired height of the thumbnail.
* @param {string} resizeMethod The thumbnail resize method to use, either
* "crop" or "scale".
* @param {Boolean} allowDirectLinks If true, return any non-mxc URLs
* directly. Fetching such URLs will leak information about the user to
* anyone they share a room with. If false, will return the emptry string
* for such URLs.
* @return {string} The complete URL to the content.
*/
export function getHttpUriForMxc(baseUrl, mxc, width, height,
resizeMethod, allowDirectLinks) {
if (typeof mxc !== "string" || !mxc) {
return '';
}
if (mxc.indexOf("mxc://") !== 0) {
if (allowDirectLinks) {
return mxc;
} else {
return '';
}
if (mxc.indexOf("mxc://") !== 0) {
if (allowDirectLinks) {
return mxc;
} else {
return '';
}
}
let serverAndMediaId = mxc.slice(6); // strips mxc://
let prefix = "/_matrix/media/v1/download/";
const params = {};
}
let serverAndMediaId = mxc.slice(6); // strips mxc://
let prefix = "/_matrix/media/r0/download/";
const params = {};
if (width) {
params.width = width;
}
if (height) {
params.height = height;
}
if (resizeMethod) {
params.method = resizeMethod;
}
if (utils.keys(params).length > 0) {
// these are thumbnailing params so they probably want the
// thumbnailing API...
prefix = "/_matrix/media/v1/thumbnail/";
}
if (width) {
params.width = Math.round(width);
}
if (height) {
params.height = Math.round(height);
}
if (resizeMethod) {
params.method = resizeMethod;
}
if (utils.keys(params).length > 0) {
// these are thumbnailing params so they probably want the
// thumbnailing API...
prefix = "/_matrix/media/r0/thumbnail/";
}
const fragmentOffset = serverAndMediaId.indexOf("#");
let fragment = "";
if (fragmentOffset >= 0) {
fragment = serverAndMediaId.substr(fragmentOffset);
serverAndMediaId = serverAndMediaId.substr(0, fragmentOffset);
}
return baseUrl + prefix + serverAndMediaId +
(utils.keys(params).length === 0 ? "" :
("?" + utils.encodeParams(params))) + fragment;
},
const fragmentOffset = serverAndMediaId.indexOf("#");
let fragment = "";
if (fragmentOffset >= 0) {
fragment = serverAndMediaId.substr(fragmentOffset);
serverAndMediaId = serverAndMediaId.substr(0, fragmentOffset);
}
return baseUrl + prefix + serverAndMediaId +
(utils.keys(params).length === 0 ? "" :
("?" + utils.encodeParams(params))) + fragment;
}
/**
* Get an identicon URL from an arbitrary string.
* @param {string} baseUrl The base homeserver url which has a content repo.
* @param {string} identiconString The string to create an identicon for.
* @param {Number} width The desired width of the image in pixels. Default: 96.
* @param {Number} height The desired height of the image in pixels. Default: 96.
* @return {string} The complete URL to the identicon.
*/
getIdenticonUri: function(baseUrl, identiconString, width, height) {
if (!identiconString) {
return null;
}
if (!width) {
width = 96;
}
if (!height) {
height = 96;
}
const params = {
width: width,
height: height,
};
/**
* Get an identicon URL from an arbitrary string.
* @param {string} baseUrl The base homeserver url which has a content repo.
* @param {string} identiconString The string to create an identicon for.
* @param {Number} width The desired width of the image in pixels. Default: 96.
* @param {Number} height The desired height of the image in pixels. Default: 96.
* @return {string} The complete URL to the identicon.
* @deprecated This is no longer in the specification.
*/
export function getIdenticonUri(baseUrl, identiconString, width, height) {
if (!identiconString) {
return null;
}
if (!width) {
width = 96;
}
if (!height) {
height = 96;
}
const params = {
width: width,
height: height,
};
const path = utils.encodeUri("/_matrix/media/v1/identicon/$ident", {
$ident: identiconString,
});
return baseUrl + path +
(utils.keys(params).length === 0 ? "" :
("?" + utils.encodeParams(params)));
},
};
const path = utils.encodeUri("/_matrix/media/unstable/identicon/$ident", {
$ident: identiconString,
});
return baseUrl + path +
(utils.keys(params).length === 0 ? "" :
("?" + utils.encodeParams(params)));
}
+679
View File
@@ -0,0 +1,679 @@
/*
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.
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.
*/
/**
* Cross signing methods
* @module crypto/CrossSigning
*/
import {decodeBase64, encodeBase64, pkSign, pkVerify} from './olmlib';
import {EventEmitter} from 'events';
import {logger} from '../logger';
import {IndexedDBCryptoStore} from '../crypto/store/indexeddb-crypto-store';
import {decryptAES, encryptAES} from './aes';
function publicKeyFromKeyInfo(keyInfo) {
// `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 {
/**
* Information about a user's cross-signing keys
*
* @class
*
* @param {string} userId the user that the information is about
* @param {object} callbacks Callbacks used to interact with the app
* Requires getCrossSigningKey and saveCrossSigningKeys
* @param {object} cacheCallbacks Callbacks used to interact with the cache
*/
constructor(userId, callbacks, cacheCallbacks) {
super();
// you can't change the userId
Object.defineProperty(this, 'userId', {
enumerable: true,
value: userId,
});
this._callbacks = callbacks || {};
this._cacheCallbacks = cacheCallbacks || {};
this.keys = {};
this.firstUse = true;
// This tracks whether we've ever verified this user with any identity.
// When you verify a user, any devices online at the time that receive
// the verifying signature via the homeserver will latch this to true
// and can use it in the future to detect cases where the user has
// become unverifed later for any reason.
this.crossSigningVerifiedBefore = false;
}
/**
* Calls the app callback to ask for a private key
* @param {string} type The key type ("master", "self_signing", or "user_signing")
* @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) {
const shouldCache = ["self_signing", "user_signing"].indexOf(type) >= 0;
if (!this._callbacks.getCrossSigningKey) {
throw new Error("No getCrossSigningKey callback supplied");
}
if (expectedPubkey === undefined) {
expectedPubkey = this.getId(type);
}
function validateKey(key) {
if (!key) return;
const signing = new global.Olm.PkSigning();
const gotPubkey = signing.init_with_seed(key);
if (gotPubkey === expectedPubkey) {
return [gotPubkey, signing];
}
signing.free();
}
let privkey;
if (this._cacheCallbacks.getCrossSigningKeyCache && shouldCache) {
privkey = await this._cacheCallbacks
.getCrossSigningKeyCache(type, expectedPubkey);
}
const cacheresult = validateKey(privkey);
if (cacheresult) {
return cacheresult;
}
privkey = await this._callbacks.getCrossSigningKey(type, expectedPubkey);
const result = validateKey(privkey);
if (result) {
if (this._cacheCallbacks.storeCrossSigningKeyCache && shouldCache) {
await this._cacheCallbacks.storeCrossSigningKeyCache(type, privkey);
}
return result;
}
/* No keysource even returned a key */
if (!privkey) {
throw new Error(
"getCrossSigningKey callback for " + type + " returned falsey",
);
}
/* We got some keys from the keysource, but none of them were valid */
throw new Error(
"Key type " + type + " from getCrossSigningKey callback did not match",
);
}
static fromStorage(obj, userId) {
const res = new CrossSigningInfo(userId);
for (const prop in obj) {
if (obj.hasOwnProperty(prop)) {
res[prop] = obj[prop];
}
}
return res;
}
toStorage() {
return {
keys: this.keys,
firstUse: this.firstUse,
crossSigningVerifiedBefore: this.crossSigningVerifiedBefore,
};
}
/**
* 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 {object} map of key name to key info the secret is encrypted
* with, or null if it is not present or not encrypted with a trusted
* key
*/
async isStoredInSecretStorage(secretStorage) {
// check what SSSS keys have encrypted the master key (if any)
const stored =
await secretStorage.isStored("m.cross_signing.master", false) || {};
// then check which of those SSSS keys have also encrypted the SSK and USK
function intersect(s) {
for (const k of Object.keys(stored)) {
if (!s[k]) {
delete stored[k];
}
}
}
for (const type of ["self_signing", "user_signing"]) {
intersect(
await secretStorage.isStored(`m.cross_signing.${type}`, false) || {},
);
}
return Object.keys(stored).length ? stored : null;
}
/**
* 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".
*
* @return {string} the ID
*/
getId(type) {
type = type || "master";
if (!this.keys[type]) return null;
const keyInfo = this.keys[type];
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");
}
// If we're resetting the master key, we reset all keys
if (
level === undefined ||
level & CrossSigningLevel.MASTER ||
!this.keys.master
) {
level = (
CrossSigningLevel.MASTER |
CrossSigningLevel.USER_SIGNING |
CrossSigningLevel.SELF_SIGNING
);
} else if (level === 0) {
return;
}
const privateKeys = {};
const keys = {};
let masterSigning;
let masterPub;
try {
if (level & CrossSigningLevel.MASTER) {
masterSigning = new global.Olm.PkSigning();
privateKeys.master = masterSigning.generate_seed();
masterPub = masterSigning.init_with_seed(privateKeys.master);
keys.master = {
user_id: this.userId,
usage: ['master'],
keys: {
['ed25519:' + masterPub]: masterPub,
},
};
} else {
[masterPub, masterSigning] = await this.getCrossSigningKey("master");
}
if (level & CrossSigningLevel.SELF_SIGNING) {
const sskSigning = new global.Olm.PkSigning();
try {
privateKeys.self_signing = sskSigning.generate_seed();
const sskPub = sskSigning.init_with_seed(privateKeys.self_signing);
keys.self_signing = {
user_id: this.userId,
usage: ['self_signing'],
keys: {
['ed25519:' + sskPub]: sskPub,
},
};
pkSign(keys.self_signing, masterSigning, this.userId, masterPub);
} finally {
sskSigning.free();
}
}
if (level & CrossSigningLevel.USER_SIGNING) {
const uskSigning = new global.Olm.PkSigning();
try {
privateKeys.user_signing = uskSigning.generate_seed();
const uskPub = uskSigning.init_with_seed(privateKeys.user_signing);
keys.user_signing = {
user_id: this.userId,
usage: ['user_signing'],
keys: {
['ed25519:' + uskPub]: uskPub,
},
};
pkSign(keys.user_signing, masterSigning, this.userId, masterPub);
} finally {
uskSigning.free();
}
}
Object.assign(this.keys, keys);
this._callbacks.saveCrossSigningKeys(privateKeys);
} finally {
if (masterSigning) {
masterSigning.free();
}
}
}
/**
* unsets the keys, used when another session has reset the keys, to disable cross-signing
*/
clearKeys() {
this.keys = {};
}
setKeys(keys) {
const signingKeys = {};
if (keys.master) {
if (keys.master.user_id !== this.userId) {
const error = "Mismatched user ID " + keys.master.user_id +
" in master key from " + this.userId;
logger.error(error);
throw new Error(error);
}
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) !== this.getId()) {
// this is a different key, so first-use is false
this.firstUse = false;
} // otherwise, same key, so no change
signingKeys.master = keys.master;
} else if (this.keys.master) {
signingKeys.master = this.keys.master;
} else {
throw new Error("Tried to set cross-signing keys without a master key");
}
const masterKey = publicKeyFromKeyInfo(signingKeys.master);
// verify signatures
if (keys.user_signing) {
if (keys.user_signing.user_id !== this.userId) {
const error = "Mismatched user ID " + keys.master.user_id +
" in user_signing key from " + this.userId;
logger.error(error);
throw new Error(error);
}
try {
pkVerify(keys.user_signing, masterKey, this.userId);
} catch (e) {
logger.error("invalid signature on user-signing key");
// FIXME: what do we want to do here?
throw e;
}
}
if (keys.self_signing) {
if (keys.self_signing.user_id !== this.userId) {
const error = "Mismatched user ID " + keys.master.user_id +
" in self_signing key from " + this.userId;
logger.error(error);
throw new Error(error);
}
try {
pkVerify(keys.self_signing, masterKey, this.userId);
} catch (e) {
logger.error("invalid signature on self-signing key");
// FIXME: what do we want to do here?
throw e;
}
}
// if everything checks out, then save the keys
if (keys.master) {
this.keys.master = keys.master;
// if the master key is set, then the old self-signing and
// user-signing keys are obsolete
this.keys.self_signing = null;
this.keys.user_signing = null;
}
if (keys.self_signing) {
this.keys.self_signing = keys.self_signing;
}
if (keys.user_signing) {
this.keys.user_signing = keys.user_signing;
}
}
updateCrossSigningVerifiedBefore(isCrossSigningVerified) {
// It is critical that this value latches forward from false to true but
// never back to false to avoid a downgrade attack.
if (!this.crossSigningVerifiedBefore && isCrossSigningVerified) {
this.crossSigningVerifiedBefore = true;
}
}
async signObject(data, type) {
if (!this.keys[type]) {
throw new Error(
"Attempted to sign with " + type + " key but no such key present",
);
}
const [pubkey, signing] = await this.getCrossSigningKey(type);
try {
pkSign(data, signing, this.userId, pubkey);
return data;
} finally {
signing.free();
}
}
async signUser(key) {
if (!this.keys.user_signing) {
logger.info("No user signing key: not signing user");
return;
}
return this.signObject(key.keys.master, "user_signing");
}
async signDevice(userId, device) {
if (userId !== this.userId) {
throw new Error(
`Trying to sign ${userId}'s device; can only sign our own device`,
);
}
if (!this.keys.self_signing) {
logger.info("No self signing key: not signing device");
return;
}
return this.signObject(
{
algorithms: device.algorithms,
keys: device.keys,
device_id: device.deviceId,
user_id: userId,
}, "self_signing",
);
}
/**
* Check whether a given user is trusted.
*
* @param {CrossSigningInfo} userCrossSigning Cross signing info for user
*
* @returns {UserTrustLevel}
*/
checkUserTrust(userCrossSigning) {
// if we're checking our own key, then it's trusted if the master key
// and self-signing key match
if (this.userId === userCrossSigning.userId
&& this.getId() && this.getId() === userCrossSigning.getId()
&& this.getId("self_signing")
&& this.getId("self_signing") === userCrossSigning.getId("self_signing")
) {
return new UserTrustLevel(true, true, this.firstUse);
}
if (!this.keys.user_signing) {
// If there's no user signing key, they can't possibly be verified.
// They may be TOFU trusted though.
return new UserTrustLevel(false, false, userCrossSigning.firstUse);
}
let userTrusted;
const userMaster = userCrossSigning.keys.master;
const uskId = this.getId('user_signing');
try {
pkVerify(userMaster, uskId, this.userId);
userTrusted = true;
} catch (e) {
userTrusted = false;
}
return new UserTrustLevel(
userTrusted,
userCrossSigning.crossSigningVerifiedBefore,
userCrossSigning.firstUse,
);
}
/**
* Check whether a given device is trusted.
*
* @param {CrossSigningInfo} userCrossSigning Cross signing info for user
* @param {module:crypto/deviceinfo} device The device to check
* @param {bool} localTrust Whether the device is trusted locally
* @param {bool} trustCrossSignedDevices Whether we trust cross signed devices
*
* @returns {DeviceTrustLevel}
*/
checkDeviceTrust(userCrossSigning, device, localTrust, trustCrossSignedDevices) {
const userTrust = this.checkUserTrust(userCrossSigning);
const userSSK = userCrossSigning.keys.self_signing;
if (!userSSK) {
// if the user has no self-signing key then we cannot make any
// trust assertions about this device from cross-signing
return new DeviceTrustLevel(
false, false, localTrust, trustCrossSignedDevices,
);
}
const deviceObj = deviceToObject(device, userCrossSigning.userId);
try {
// if we can verify the user's SSK from their master key...
pkVerify(userSSK, userCrossSigning.getId(), userCrossSigning.userId);
// ...and this device's key from their SSK...
pkVerify(
deviceObj, publicKeyFromKeyInfo(userSSK), userCrossSigning.userId,
);
// ...then we trust this device as much as far as we trust the user
return DeviceTrustLevel.fromUserTrustLevel(
userTrust, localTrust, trustCrossSignedDevices,
);
} catch (e) {
return new DeviceTrustLevel(
false, false, localTrust, trustCrossSignedDevices,
);
}
}
/**
* @returns {object} Cache callbacks
*/
getCacheCallbacks() {
return this._cacheCallbacks;
}
}
function deviceToObject(device, userId) {
return {
algorithms: device.algorithms,
keys: device.keys,
device_id: device.deviceId,
user_id: userId,
signatures: device.signatures,
};
}
export const CrossSigningLevel = {
MASTER: 4,
USER_SIGNING: 2,
SELF_SIGNING: 1,
};
/**
* Represents the ways in which we trust a user
*/
export class UserTrustLevel {
constructor(crossSigningVerified, crossSigningVerifiedBefore, tofu) {
this._crossSigningVerified = crossSigningVerified;
this._crossSigningVerifiedBefore = crossSigningVerifiedBefore;
this._tofu = tofu;
}
/**
* @returns {bool} true if this user is verified via any means
*/
isVerified() {
return this.isCrossSigningVerified();
}
/**
* @returns {bool} true if this user is verified via cross signing
*/
isCrossSigningVerified() {
return this._crossSigningVerified;
}
/**
* @returns {bool} true if we ever verified this user before (at least for
* the history of verifications observed by this device).
*/
wasCrossSigningVerified() {
return this._crossSigningVerifiedBefore;
}
/**
* @returns {bool} true if this user's key is trusted on first use
*/
isTofu() {
return this._tofu;
}
}
/**
* Represents the ways in which we trust a device
*/
export class DeviceTrustLevel {
constructor(crossSigningVerified, tofu, localVerified, trustCrossSignedDevices) {
this._crossSigningVerified = crossSigningVerified;
this._tofu = tofu;
this._localVerified = localVerified;
this._trustCrossSignedDevices = trustCrossSignedDevices;
}
static fromUserTrustLevel(userTrustLevel, localVerified, trustCrossSignedDevices) {
return new DeviceTrustLevel(
userTrustLevel._crossSigningVerified,
userTrustLevel._tofu,
localVerified,
trustCrossSignedDevices,
);
}
/**
* @returns {bool} true if this device is verified via any means
*/
isVerified() {
return Boolean(this.isLocallyVerified() || (
this._trustCrossSignedDevices && this.isCrossSigningVerified()
));
}
/**
* @returns {bool} true if this device is verified via cross signing
*/
isCrossSigningVerified() {
return this._crossSigningVerified;
}
/**
* @returns {bool} true if this device is verified locally
*/
isLocallyVerified() {
return this._localVerified;
}
/**
* @returns {bool} true if this device is trusted from a user's key
* that is trusted on first use
*/
isTofu() {
return this._tofu;
}
}
export function createCryptoStoreCacheCallbacks(store, olmdevice) {
return {
getCrossSigningKeyCache: async function(type, _expectedPublicKey) {
const key = await new Promise((resolve) => {
return store.doTxn(
'readonly',
[IndexedDBCryptoStore.STORE_ACCOUNT],
(txn) => {
store.getSecretStorePrivateKey(txn, resolve, type);
},
);
});
if (key && key.ciphertext) {
const pickleKey = Buffer.from(olmdevice._pickleKey);
const decrypted = await decryptAES(key, pickleKey, type);
return decodeBase64(decrypted);
} else {
return key;
}
},
storeCrossSigningKeyCache: async function(type, key) {
if (!(key instanceof Uint8Array)) {
throw new Error(
`storeCrossSigningKeyCache expects Uint8Array, got ${key}`,
);
}
const pickleKey = Buffer.from(olmdevice._pickleKey);
key = await encryptAES(encodeBase64(key), pickleKey, type);
return store.doTxn(
'readwrite',
[IndexedDBCryptoStore.STORE_ACCOUNT],
(txn) => {
store.storeSecretStorePrivateKey(txn, type, key);
},
);
},
};
}
+446 -114
View File
@@ -1,5 +1,7 @@
/*
Copyright 2017 Vector Creations Ltd
Copyright 2018, 2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,7 +15,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
"use strict";
/**
* @module crypto/DeviceList
@@ -21,10 +22,13 @@ limitations under the License.
* Manages the list of other users' devices
*/
import Promise from 'bluebird';
import DeviceInfo from './deviceinfo';
import olmlib from './olmlib';
import {EventEmitter} from 'events';
import {logger} from '../logger';
import {DeviceInfo} from './deviceinfo';
import {CrossSigningInfo} from './CrossSigning';
import * as olmlib from './olmlib';
import {IndexedDBCryptoStore} from './store/indexeddb-crypto-store';
import {defer, sleep} from '../utils';
/* State transition diagram for DeviceList._deviceTrackingStatus
@@ -57,32 +61,206 @@ const TRACKING_STATUS_UP_TO_DATE = 3;
/**
* @alias module:crypto/DeviceList
*/
export default class DeviceList {
constructor(baseApis, sessionStore, olmDevice) {
this._sessionStore = sessionStore;
this._serialiser = new DeviceListUpdateSerialiser(
baseApis, sessionStore, olmDevice,
);
export class DeviceList extends EventEmitter {
constructor(baseApis, cryptoStore, olmDevice) {
super();
this._cryptoStore = cryptoStore;
// userId -> {
// deviceId -> {
// [device info]
// }
// }
this._devices = {};
// userId -> {
// [key info]
// }
this._crossSigningInfo = {};
// map of identity keys to the user who owns it
this._userByIdentityKey = {};
// which users we are tracking device status for.
// userId -> TRACKING_STATUS_*
this._deviceTrackingStatus = sessionStore.getEndToEndDeviceTrackingStatus() || {};
this._deviceTrackingStatus = {}; // loaded from storage in load()
// The 'next_batch' sync token at the point the data was writen,
// ie. a token representing the point immediately after the
// moment represented by the snapshot in the db.
this._syncToken = null;
this._serialiser = new DeviceListUpdateSerialiser(
baseApis, olmDevice, this,
);
// userId -> promise
this._keyDownloadsInProgressByUser = {};
// Set whenever changes are made other than setting the sync token
this._dirty = false;
// Promise resolved when device data is saved
this._savePromise = null;
// Function that resolves the save promise
this._resolveSavePromise = null;
// The time the save is scheduled for
this._savePromiseTime = null;
// The timer used to delay the save
this._saveTimer = null;
// True if we have fetched data from the server or loaded a non-empty
// set of device data from the store
this._hasFetched = null;
}
/**
* Load the device tracking state from storage
*/
async load() {
await this._cryptoStore.doTxn(
'readonly', [IndexedDBCryptoStore.STORE_DEVICE_DATA], (txn) => {
this._cryptoStore.getEndToEndDeviceData(txn, (deviceData) => {
this._hasFetched = Boolean(deviceData && deviceData.devices);
this._devices = deviceData ? deviceData.devices : {},
this._crossSigningInfo = deviceData ?
deviceData.crossSigningInfo || {} : {};
this._deviceTrackingStatus = deviceData ?
deviceData.trackingStatus : {};
this._syncToken = deviceData ? deviceData.syncToken : null;
this._userByIdentityKey = {};
for (const user of Object.keys(this._devices)) {
const userDevices = this._devices[user];
for (const device of Object.keys(userDevices)) {
const idKey = userDevices[device].keys['curve25519:'+device];
if (idKey !== undefined) {
this._userByIdentityKey[idKey] = user;
}
}
}
});
},
);
for (const u of Object.keys(this._deviceTrackingStatus)) {
// if a download was in progress when we got shut down, it isn't any more.
if (this._deviceTrackingStatus[u] == TRACKING_STATUS_DOWNLOAD_IN_PROGRESS) {
this._deviceTrackingStatus[u] = TRACKING_STATUS_PENDING_DOWNLOAD;
}
}
}
// userId -> promise
this._keyDownloadsInProgressByUser = {};
this.lastKnownSyncToken = null;
stop() {
if (this._saveTimer !== null) {
clearTimeout(this._saveTimer);
}
}
/**
* Download the keys for a list of users and stores the keys in the session
* store.
* Save the device tracking state to storage, if any changes are
* pending other than updating the sync token
*
* The actual save will be delayed by a short amount of time to
* aggregate multiple writes to the database.
*
* @param {integer} delay Time in ms before which the save actually happens.
* By default, the save is delayed for a short period in order to batch
* multiple writes, but this behaviour can be disabled by passing 0.
*
* @return {Promise<bool>} true if the data was saved, false if
* it was not (eg. because no changes were pending). The promise
* will only resolve once the data is saved, so may take some time
* to resolve.
*/
async saveIfDirty(delay) {
if (!this._dirty) return Promise.resolve(false);
// Delay saves for a bit so we can aggregate multiple saves that happen
// in quick succession (eg. when a whole room's devices are marked as known)
if (delay === undefined) delay = 500;
const targetTime = Date.now + delay;
if (this._savePromiseTime && targetTime < this._savePromiseTime) {
// There's a save scheduled but for after we would like: cancel
// it & schedule one for the time we want
clearTimeout(this._saveTimer);
this._saveTimer = null;
this._savePromiseTime = null;
// (but keep the save promise since whatever called save before
// will still want to know when the save is done)
}
let savePromise = this._savePromise;
if (savePromise === null) {
savePromise = new Promise((resolve, reject) => {
this._resolveSavePromise = resolve;
});
this._savePromise = savePromise;
}
if (this._saveTimer === null) {
const resolveSavePromise = this._resolveSavePromise;
this._savePromiseTime = targetTime;
this._saveTimer = setTimeout(() => {
logger.log('Saving device tracking data', this._syncToken);
// null out savePromise now (after the delay but before the write),
// otherwise we could return the existing promise when the save has
// actually already happened.
this._savePromiseTime = null;
this._saveTimer = null;
this._savePromise = null;
this._resolveSavePromise = null;
this._cryptoStore.doTxn(
'readwrite', [IndexedDBCryptoStore.STORE_DEVICE_DATA], (txn) => {
this._cryptoStore.storeEndToEndDeviceData({
devices: this._devices,
crossSigningInfo: this._crossSigningInfo,
trackingStatus: this._deviceTrackingStatus,
syncToken: this._syncToken,
}, txn);
},
).then(() => {
// The device list is considered dirty until the write
// completes.
this._dirty = false;
resolveSavePromise();
}, err => {
logger.error('Failed to save device tracking data', this._syncToken);
logger.error(err);
});
}, delay);
}
return savePromise;
}
/**
* Gets the sync token last set with setSyncToken
*
* @return {string} The sync token
*/
getSyncToken() {
return this._syncToken;
}
/**
* Sets the sync token that the app will pass as the 'since' to the /sync
* endpoint next time it syncs.
* The sync token must always be set after any changes made as a result of
* data in that sync since setting the sync token to a newer one will mean
* those changed will not be synced from the server if a new client starts
* up with that data.
*
* @param {string} st The sync token
*/
setSyncToken(st) {
this._syncToken = st;
}
/**
* Ensures up to date keys for a list of users are stored in the session store,
* downloading and storing them if they're not (or if forceDownload is
* true).
* @param {Array} userIds The users to fetch.
* @param {bool} forceDownload Always download the keys even if cached.
*
@@ -98,7 +276,7 @@ export default class DeviceList {
if (this._keyDownloadsInProgressByUser[u]) {
// already a key download in progress/queued for this user; its results
// will be good enough for us.
console.log(
logger.log(
`downloadKeys: already have a download in progress for ` +
`${u}: awaiting its result`,
);
@@ -109,13 +287,13 @@ export default class DeviceList {
});
if (usersToDownload.length != 0) {
console.log("downloadKeys: downloading for", usersToDownload);
logger.log("downloadKeys: downloading for", usersToDownload);
const downloadPromise = this._doKeyDownload(usersToDownload);
promises.push(downloadPromise);
}
if (promises.length === 0) {
console.log("downloadKeys: already have all necessary keys");
logger.log("downloadKeys: already have all necessary keys");
}
return Promise.all(promises).then(() => {
@@ -143,6 +321,15 @@ export default class DeviceList {
return stored;
}
/**
* Returns a list of all user IDs the DeviceList knows about
*
* @return {array} All known user IDs
*/
getKnownUserIds() {
return Object.keys(this._devices);
}
/**
* Get the stored device keys for a user id
*
@@ -152,7 +339,7 @@ export default class DeviceList {
* managed to get a list of devices for this user yet.
*/
getStoredDevicesForUser(userId) {
const devs = this._sessionStore.getEndToEndDevicesForUser(userId);
const devs = this._devices[userId];
if (!devs) {
return null;
}
@@ -165,6 +352,29 @@ export default class DeviceList {
return res;
}
/**
* Get the stored device data for a user, in raw object form
*
* @param {string} userId the user to get data for
*
* @return {Object} deviceId->{object} devices, or undefined if
* there is no data for this user.
*/
getRawStoredDevicesForUser(userId) {
return this._devices[userId];
}
getStoredCrossSigningForUser(userId) {
if (!this._crossSigningInfo[userId]) return null;
return CrossSigningInfo.fromStorage(this._crossSigningInfo[userId], userId);
}
storeCrossSigningForUser(userId, info) {
this._crossSigningInfo[userId] = info;
this._dirty = true;
}
/**
* Get the stored keys for a single device
*
@@ -175,7 +385,7 @@ export default class DeviceList {
* if we don't know about this device
*/
getStoredDevice(userId, deviceId) {
const devs = this._sessionStore.getEndToEndDevicesForUser(userId);
const devs = this._devices[userId];
if (!devs || !devs[deviceId]) {
return undefined;
}
@@ -183,15 +393,14 @@ export default class DeviceList {
}
/**
* Find a device by curve25519 identity key
* Get a user ID by one of their device's curve25519 identity key
*
* @param {string} userId owner of the device
* @param {string} algorithm encryption algorithm
* @param {string} senderKey curve25519 key to match
*
* @return {module:crypto/deviceinfo?}
* @return {string} user ID
*/
getDeviceByIdentityKey(userId, algorithm, senderKey) {
getUserByIdentityKey(algorithm, senderKey) {
if (
algorithm !== olmlib.OLM_ALGORITHM &&
algorithm !== olmlib.MEGOLM_ALGORITHM
@@ -200,7 +409,24 @@ export default class DeviceList {
return null;
}
const devices = this._sessionStore.getEndToEndDevicesForUser(userId);
return this._userByIdentityKey[senderKey];
}
/**
* Find a device by curve25519 identity key
*
* @param {string} algorithm encryption algorithm
* @param {string} senderKey curve25519 key to match
*
* @return {module:crypto/deviceinfo?}
*/
getDeviceByIdentityKey(algorithm, senderKey) {
const userId = this.getUserByIdentityKey(algorithm, senderKey);
if (!userId) {
return null;
}
const devices = this._devices[userId];
if (!devices) {
return null;
}
@@ -229,6 +455,33 @@ export default class DeviceList {
return null;
}
/**
* Replaces the list of devices for a user with the given device list
*
* @param {string} u The user ID
* @param {Object} devs New device info for user
*/
storeDevicesForUser(u, devs) {
// remove previous devices from _userByIdentityKey
if (this._devices[u] !== undefined) {
for (const [deviceId, dev] of Object.entries(this._devices[u])) {
const identityKey = dev.keys['curve25519:'+deviceId];
delete this._userByIdentityKey[identityKey];
}
}
this._devices[u] = devs;
// add new ones
for (const [deviceId, dev] of Object.entries(devs)) {
const identityKey = dev.keys['curve25519:'+deviceId];
this._userByIdentityKey[identityKey] = u;
}
this._dirty = true;
}
/**
* flag the given user for device-list tracking, if they are not already.
*
@@ -250,12 +503,12 @@ export default class DeviceList {
throw new Error('userId must be a string; was '+userId);
}
if (!this._deviceTrackingStatus[userId]) {
console.log('Now tracking device list for ' + userId);
logger.log('Now tracking device list for ' + userId);
this._deviceTrackingStatus[userId] = TRACKING_STATUS_PENDING_DOWNLOAD;
// we don't yet persist the tracking status, since there may be a lot
// of calls; we save all data together once the sync is done
this._dirty = true;
}
// we don't yet persist the tracking status, since there may be a lot
// of calls; instead we wait for the forthcoming
// refreshOutdatedDeviceLists.
}
/**
@@ -269,14 +522,27 @@ export default class DeviceList {
*/
stopTrackingDeviceList(userId) {
if (this._deviceTrackingStatus[userId]) {
console.log('No longer tracking device list for ' + userId);
logger.log('No longer tracking device list for ' + userId);
this._deviceTrackingStatus[userId] = TRACKING_STATUS_NOT_TRACKED;
// we don't yet persist the tracking status, since there may be a lot
// of calls; we save all data together once the sync is done
this._dirty = true;
}
// we don't yet persist the tracking status, since there may be a lot
// of calls; instead we wait for the forthcoming
// refreshOutdatedDeviceLists.
}
/**
* Set all users we're currently tracking to untracked
*
* This will flag each user whose devices we are tracking as in need of an
* update.
*/
stopTrackingAllDeviceLists() {
for (const userId of Object.keys(this._deviceTrackingStatus)) {
this._deviceTrackingStatus[userId] = TRACKING_STATUS_NOT_TRACKED;
}
this._dirty = true;
}
/**
* Mark the cached device list for the given user outdated.
@@ -291,23 +557,12 @@ export default class DeviceList {
*/
invalidateUserDeviceList(userId) {
if (this._deviceTrackingStatus[userId]) {
console.log("Marking device list outdated for", userId);
logger.log("Marking device list outdated for", userId);
this._deviceTrackingStatus[userId] = TRACKING_STATUS_PENDING_DOWNLOAD;
}
// we don't yet persist the tracking status, since there may be a lot
// of calls; instead we wait for the forthcoming
// refreshOutdatedDeviceLists.
}
/**
* Mark all tracked device lists as outdated.
*
* This will flag each user whose devices we are tracking as in need of an
* update.
*/
invalidateAllDeviceLists() {
for (const userId of Object.keys(this._deviceTrackingStatus)) {
this.invalidateUserDeviceList(userId);
// we don't yet persist the tracking status, since there may be a lot
// of calls; we save all data together once the sync is done
this._dirty = true;
}
}
@@ -318,6 +573,8 @@ export default class DeviceList {
* is no need to wait for this (it's mostly for the unit tests).
*/
refreshOutdatedDeviceLists() {
this.saveIfDirty();
const usersToDownload = [];
for (const userId of Object.keys(this._deviceTrackingStatus)) {
const stat = this._deviceTrackingStatus[userId];
@@ -326,13 +583,40 @@ export default class DeviceList {
}
}
// we didn't persist the tracking status during
// invalidateUserDeviceList, so do it now.
this._persistDeviceTrackingStatus();
return this._doKeyDownload(usersToDownload);
}
/**
* Set the stored device data for a user, in raw object form
* Used only by internal class DeviceListUpdateSerialiser
*
* @param {string} userId the user to get data for
*
* @param {Object} devices deviceId->{object} the new devices
*/
_setRawStoredDevicesForUser(userId, devices) {
// remove old devices from _userByIdentityKey
if (this._devices[userId] !== undefined) {
for (const [deviceId, dev] of Object.entries(this._devices[userId])) {
const identityKey = dev.keys['curve25519:'+deviceId];
delete this._userByIdentityKey[identityKey];
}
}
this._devices[userId] = devices;
// add new devices into _userByIdentityKey
for (const [deviceId, dev] of Object.entries(devices)) {
const identityKey = dev.keys['curve25519:'+deviceId];
this._userByIdentityKey[identityKey] = userId;
}
}
setRawStoredCrossSigningForUser(userId, info) {
this._crossSigningInfo[userId] = info;
}
/**
* Fire off download update requests for the given users, and update the
@@ -341,7 +625,7 @@ export default class DeviceList {
*
* @param {String[]} users list of userIds
*
* @return {module:client.Promise} resolves when all the users listed have
* @return {Promise} resolves when all the users listed have
* been updated. rejects if there was a problem updating any of the
* users.
*/
@@ -352,11 +636,11 @@ export default class DeviceList {
}
const prom = this._serialiser.updateDevicesForUsers(
users, this.lastKnownSyncToken,
users, this._syncToken,
).then(() => {
finished(true);
}, (e) => {
console.error(
logger.error(
'Error downloading keys for ' + users + ":", e,
);
finished(false);
@@ -372,12 +656,15 @@ export default class DeviceList {
});
const finished = (success) => {
this.emit("crypto.willUpdateDevices", users, !this._hasFetched);
users.forEach((u) => {
this._dirty = true;
// we may have queued up another download request for this user
// since we started this request. If that happens, we should
// ignore the completion of the first one.
if (this._keyDownloadsInProgressByUser[u] !== prom) {
console.log('Another update in the queue for', u,
logger.log('Another update in the queue for', u,
'- not marking up-to-date');
return;
}
@@ -388,21 +675,19 @@ export default class DeviceList {
// we didn't get any new invalidations since this download started:
// this user's device list is now up to date.
this._deviceTrackingStatus[u] = TRACKING_STATUS_UP_TO_DATE;
console.log("Device list for", u, "now up to date");
logger.log("Device list for", u, "now up to date");
} else {
this._deviceTrackingStatus[u] = TRACKING_STATUS_PENDING_DOWNLOAD;
}
}
});
this._persistDeviceTrackingStatus();
this.saveIfDirty();
this.emit("crypto.devicesUpdated", users, !this._hasFetched);
this._hasFetched = true;
};
return prom;
}
_persistDeviceTrackingStatus() {
this._sessionStore.storeEndToEndDeviceTrackingStatus(this._deviceTrackingStatus);
}
}
/**
@@ -415,10 +700,15 @@ export default class DeviceList {
* time (and queuing other requests up).
*/
class DeviceListUpdateSerialiser {
constructor(baseApis, sessionStore, olmDevice) {
/*
* @param {object} baseApis Base API object
* @param {object} olmDevice The Olm Device
* @param {object} deviceList The device list object
*/
constructor(baseApis, olmDevice, deviceList) {
this._baseApis = baseApis;
this._sessionStore = sessionStore;
this._olmDevice = olmDevice;
this._deviceList = deviceList; // the device list to be updated
this._downloadInProgress = false;
@@ -431,9 +721,7 @@ class DeviceListUpdateSerialiser {
// non-null indicates that we have users queued for download.
this._queuedQueryDeferred = null;
// sync token to be used for the next query: essentially the
// most recent one we know about
this._nextSyncToken = null;
this._syncToken = null; // The sync token we send with the requests
}
/**
@@ -444,7 +732,7 @@ class DeviceListUpdateSerialiser {
* @param {String} syncToken sync token to pass in the query request, to
* help the HS give the most recent results
*
* @return {module:client.Promise} resolves when all the users listed have
* @return {Promise} resolves when all the users listed have
* been updated. rejects if there was a problem updating any of the
* users.
*/
@@ -452,15 +740,19 @@ class DeviceListUpdateSerialiser {
users.forEach((u) => {
this._keyDownloadsQueuedByUser[u] = true;
});
this._nextSyncToken = syncToken;
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
// been given, since it just needs to be at least as recent as the
// sync response the device invalidation message arrived in
this._syncToken = syncToken;
if (this._downloadInProgress) {
// just queue up these users
console.log('Queued key download for', users);
logger.log('Queued key download for', users);
return this._queuedQueryDeferred.promise;
}
@@ -480,34 +772,46 @@ class DeviceListUpdateSerialiser {
const deferred = this._queuedQueryDeferred;
this._queuedQueryDeferred = null;
console.log('Starting key download for', downloadUsers);
logger.log('Starting key download for', downloadUsers);
this._downloadInProgress = true;
const opts = {};
if (this._nextSyncToken) {
opts.token = this._nextSyncToken;
if (this._syncToken) {
opts.token = this._syncToken;
}
this._baseApis.downloadKeysForUsers(
downloadUsers, opts,
).then((res) => {
).then(async (res) => {
const dk = res.device_keys || {};
const masterKeys = res.master_keys || {};
const ssks = res.self_signing_keys || {};
const usks = res.user_signing_keys || {};
// do each user in a separate promise, to avoid wedging the CPU
// yield to other things that want to execute in between users, to
// avoid wedging the CPU
// (https://github.com/vector-im/riot-web/issues/3158)
//
// of course we ought to do this in a web worker or similar, but
// this serves as an easy solution for now.
let prom = Promise.resolve();
for (const userId of downloadUsers) {
prom = prom.delay(5).then(() => {
return this._processQueryResponseForUser(userId, dk[userId]);
});
await sleep(5);
try {
await this._processQueryResponseForUser(
userId, dk[userId], {
master: masterKeys[userId],
self_signing: ssks[userId],
user_signing: usks[userId],
},
);
} catch (e) {
// log the error but continue, so that one bad key
// doesn't kill the whole process
logger.error(`Error processing keys for ${userId}:`, e);
}
}
return prom;
}).done(() => {
console.log('Completed key download for ' + downloadUsers);
}).then(() => {
logger.log('Completed key download for ' + downloadUsers);
this._downloadInProgress = false;
deferred.resolve();
@@ -517,7 +821,7 @@ class DeviceListUpdateSerialiser {
this._doQueuedQueries();
}
}, (e) => {
console.warn('Error downloading keys for ' + downloadUsers + ':', e);
logger.warn('Error downloading keys for ' + downloadUsers + ':', e);
this._downloadInProgress = false;
deferred.reject(e);
});
@@ -525,32 +829,58 @@ class DeviceListUpdateSerialiser {
return deferred.promise;
}
async _processQueryResponseForUser(userId, response) {
console.log('got keys for ' + userId + ':', response);
async _processQueryResponseForUser(
userId, dkResponse, crossSigningResponse,
) {
logger.log('got device keys for ' + userId + ':', dkResponse);
logger.log('got cross-signing keys for ' + userId + ':', crossSigningResponse);
// map from deviceid -> deviceinfo for this user
const userStore = {};
const devs = this._sessionStore.getEndToEndDevicesForUser(userId);
if (devs) {
Object.keys(devs).forEach((deviceId) => {
const d = DeviceInfo.fromStorage(devs[deviceId], deviceId);
userStore[deviceId] = d;
{
// map from deviceid -> deviceinfo for this user
const userStore = {};
const devs = this._deviceList.getRawStoredDevicesForUser(userId);
if (devs) {
Object.keys(devs).forEach((deviceId) => {
const d = DeviceInfo.fromStorage(devs[deviceId], deviceId);
userStore[deviceId] = d;
});
}
await _updateStoredDeviceKeysForUser(
this._olmDevice, userId, userStore, dkResponse || {},
);
// put the updates into the object that will be returned as our results
const storage = {};
Object.keys(userStore).forEach((deviceId) => {
storage[deviceId] = userStore[deviceId].toStorage();
});
this._deviceList._setRawStoredDevicesForUser(userId, storage);
}
await _updateStoredDeviceKeysForUser(
this._olmDevice, userId, userStore, response || {},
);
// now do the same for the cross-signing keys
{
// FIXME: should we be ignoring empty cross-signing responses, or
// should we be dropping the keys?
if (crossSigningResponse
&& (crossSigningResponse.master || crossSigningResponse.self_signing
|| crossSigningResponse.user_signing)) {
const crossSigning
= this._deviceList.getStoredCrossSigningForUser(userId)
|| new CrossSigningInfo(userId);
// update the session store
const storage = {};
Object.keys(userStore).forEach((deviceId) => {
storage[deviceId] = userStore[deviceId].toStorage();
});
crossSigning.setKeys(crossSigningResponse);
this._sessionStore.storeEndToEndDevicesForUser(
userId, storage,
);
this._deviceList.setRawStoredCrossSigningForUser(
userId, crossSigning.toStorage(),
);
// NB. Unlike most events in the js-sdk, this one is internal to the
// js-sdk and is not re-emitted
this._deviceList.emit('userCrossSigningUpdated', userId);
}
}
}
}
@@ -566,7 +896,7 @@ async function _updateStoredDeviceKeysForUser(_olmDevice, userId, userStore,
}
if (!(deviceId in userResult)) {
console.log("Device " + userId + ":" + deviceId +
logger.log("Device " + userId + ":" + deviceId +
" has been removed");
delete userStore[deviceId];
updated = true;
@@ -583,12 +913,12 @@ async function _updateStoredDeviceKeysForUser(_olmDevice, userId, userStore,
// check that the user_id and device_id in the response object are
// correct
if (deviceResult.user_id !== userId) {
console.warn("Mismatched user_id " + deviceResult.user_id +
logger.warn("Mismatched user_id " + deviceResult.user_id +
" in keys from " + userId + ":" + deviceId);
continue;
}
if (deviceResult.device_id !== deviceId) {
console.warn("Mismatched device_id " + deviceResult.device_id +
logger.warn("Mismatched device_id " + deviceResult.device_id +
" in keys from " + userId + ":" + deviceId);
continue;
}
@@ -618,17 +948,18 @@ async function _storeDeviceKeys(_olmDevice, userStore, deviceResult) {
const signKeyId = "ed25519:" + deviceId;
const signKey = deviceResult.keys[signKeyId];
if (!signKey) {
console.warn("Device " + userId + ":" + deviceId +
logger.warn("Device " + userId + ":" + deviceId +
" has no ed25519 key");
return false;
}
const unsigned = deviceResult.unsigned || {};
const signatures = deviceResult.signatures || {};
try {
await olmlib.verifySignature(_olmDevice, deviceResult, userId, deviceId, signKey);
} catch (e) {
console.warn("Unable to verify signature on device " +
logger.warn("Unable to verify signature on device " +
userId + ":" + deviceId + ":" + e);
return false;
}
@@ -645,7 +976,7 @@ async function _storeDeviceKeys(_olmDevice, userStore, deviceResult) {
// best off sticking with the original keys.
//
// Should we warn the user about it somehow?
console.warn("Ed25519 key for device " + userId + ":" +
logger.warn("Ed25519 key for device " + userId + ":" +
deviceId + " has changed");
return false;
}
@@ -656,5 +987,6 @@ async function _storeDeviceKeys(_olmDevice, userStore, deviceResult) {
deviceStore.keys = deviceResult.keys || {};
deviceStore.algorithms = deviceResult.algorithms || [];
deviceStore.unsigned = unsigned;
deviceStore.signatures = signatures;
return true;
}
+799 -404
View File
File diff suppressed because it is too large Load Diff
+191 -52
View File
@@ -14,9 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import Promise from 'bluebird';
import utils from '../utils';
import {logger} from '../logger';
import * as utils from '../utils';
/**
* Internal module. Management of outgoing room key requests.
@@ -35,13 +34,19 @@ const SEND_KEY_REQUESTS_DELAY_MS = 500;
*
* The state machine looks like:
*
* |
* V (cancellation requested)
* UNSENT -----------------------------+
* | |
* | (send successful) |
* V |
* SENT |
* | (cancellation sent)
* | .-------------------------------------------------.
* | | |
* V V (cancellation requested) |
* UNSENT -----------------------------+ |
* | | |
* | | |
* | (send successful) | CANCELLATION_PENDING_AND_WILL_RESEND
* V | Λ
* SENT | |
* |-------------------------------- | --------------'
* | | (cancellation requested with intent
* | | to resend the original request)
* | |
* | (cancellation requested) |
* V |
@@ -53,7 +58,7 @@ const SEND_KEY_REQUESTS_DELAY_MS = 500;
*
* @enum {number}
*/
const ROOM_KEY_REQUEST_STATES = {
export const ROOM_KEY_REQUEST_STATES = {
/** request not yet sent */
UNSENT: 0,
@@ -62,9 +67,15 @@ const ROOM_KEY_REQUEST_STATES = {
/** reply received, cancellation not yet sent */
CANCELLATION_PENDING: 2,
/**
* Cancellation not yet sent and will transition to UNSENT instead of
* being deleted once the cancellation has been sent.
*/
CANCELLATION_PENDING_AND_WILL_RESEND: 3,
};
export default class OutgoingRoomKeyRequestManager {
export class OutgoingRoomKeyRequestManager {
constructor(baseApis, deviceId, cryptoStore) {
this._baseApis = baseApis;
this._deviceId = deviceId;
@@ -86,23 +97,26 @@ export default class OutgoingRoomKeyRequestManager {
*/
start() {
this._clientRunning = true;
// set the timer going, to handle any requests which didn't get sent
// on the previous run of the client.
this._startTimer();
}
/**
* Called when the client is stopped. Stops any running background processes.
*/
stop() {
console.log('stopping OutgoingRoomKeyRequestManager');
logger.log('stopping OutgoingRoomKeyRequestManager');
// stop the timer on the next run
this._clientRunning = false;
}
/**
* Send off a room key request, if we haven't already done so.
* Send any requests that have been queued
*/
sendQueuedRequests() {
this._startTimer();
}
/**
* Queue up a room key request, if we haven't already queued or sent one.
*
* The `requestBody` is compared (with a deep-equality check) against
* previous queued or sent requests and if it matches, no change is made.
@@ -111,26 +125,108 @@ export default class OutgoingRoomKeyRequestManager {
*
* @param {module:crypto~RoomKeyRequestBody} requestBody
* @param {Array<{userId: string, deviceId: string}>} recipients
* @param {boolean} resend whether to resend the key request if there is
* already one
*
* @returns {Promise} resolves when the request has been added to the
* pending list (or we have established that a similar request already
* exists)
*/
sendRoomKeyRequest(requestBody, recipients) {
return this._cryptoStore.getOrAddOutgoingRoomKeyRequest({
requestBody: requestBody,
recipients: recipients,
requestId: this._baseApis.makeTxnId(),
state: ROOM_KEY_REQUEST_STATES.UNSENT,
}).then((req) => {
if (req.state === ROOM_KEY_REQUEST_STATES.UNSENT) {
this._startTimer();
async queueRoomKeyRequest(requestBody, recipients, resend=false) {
const req = await this._cryptoStore.getOutgoingRoomKeyRequest(
requestBody,
);
if (!req) {
await this._cryptoStore.getOrAddOutgoingRoomKeyRequest({
requestBody: requestBody,
recipients: recipients,
requestId: this._baseApis.makeTxnId(),
state: ROOM_KEY_REQUEST_STATES.UNSENT,
});
} else {
switch (req.state) {
case ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING_AND_WILL_RESEND:
case ROOM_KEY_REQUEST_STATES.UNSENT:
// nothing to do here, since we're going to send a request anyways
return;
case ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING: {
// existing request is about to be cancelled. If we want to
// resend, then change the state so that it resends after
// cancelling. Otherwise, just cancel the cancellation.
const state = resend ?
ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING_AND_WILL_RESEND :
ROOM_KEY_REQUEST_STATES.SENT;
await this._cryptoStore.updateOutgoingRoomKeyRequest(
req.requestId, ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING, {
state,
cancellationTxnId: this._baseApis.makeTxnId(),
},
);
break;
}
});
case ROOM_KEY_REQUEST_STATES.SENT: {
// a request has already been sent. If we don't want to
// resend, then do nothing. If we do want to, then cancel the
// existing request and send a new one.
if (resend) {
const state =
ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING_AND_WILL_RESEND;
const updatedReq =
await this._cryptoStore.updateOutgoingRoomKeyRequest(
req.requestId, ROOM_KEY_REQUEST_STATES.SENT, {
state,
cancellationTxnId: this._baseApis.makeTxnId(),
// need to use a new transaction ID so that
// the request gets sent
requestTxnId: this._baseApis.makeTxnId(),
},
);
if (!updatedReq) {
// updateOutgoingRoomKeyRequest couldn't find the request
// in state ROOM_KEY_REQUEST_STATES.SENT, so we must have
// raced with another tab to mark the request cancelled.
// Try again, to make sure the request is resent.
return await this.queueRoomKeyRequest(
requestBody, recipients, resend,
);
}
// We don't want to wait for the timer, so we send it
// immediately. (We might actually end up racing with the timer,
// but that's ok: even if we make the request twice, we'll do it
// with the same transaction_id, so only one message will get
// sent).
//
// (We also don't want to wait for the response from the server
// here, as it will slow down processing of received keys if we
// do.)
try {
await this._sendOutgoingRoomKeyRequestCancellation(
updatedReq,
true,
);
} catch (e) {
logger.error(
"Error sending room key request cancellation;"
+ " will retry later.", e,
);
}
// The request has transitioned from
// CANCELLATION_PENDING_AND_WILL_RESEND to UNSENT. We
// still need to resend the request which is now UNSENT, so
// start the timer if it isn't already started.
}
break;
}
default:
throw new Error('unhandled state: ' + req.state);
}
}
}
/**
* Cancel room key requests, if any match the given details
* Cancel room key requests, if any match the given requestBody
*
* @param {module:crypto~RoomKeyRequestBody} requestBody
*
@@ -147,6 +243,7 @@ export default class OutgoingRoomKeyRequestManager {
}
switch (req.state) {
case ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING:
case ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING_AND_WILL_RESEND:
// nothing to do here
return;
@@ -158,7 +255,7 @@ export default class OutgoingRoomKeyRequestManager {
// may have seen it, so we still need to send a cancellation
// in that case :/
console.log(
logger.log(
'deleting unnecessary room key request for ' +
stringifyRequestBody(requestBody),
);
@@ -166,7 +263,7 @@ export default class OutgoingRoomKeyRequestManager {
req.requestId, ROOM_KEY_REQUEST_STATES.UNSENT,
);
case ROOM_KEY_REQUEST_STATES.SENT:
case ROOM_KEY_REQUEST_STATES.SENT: {
// send a cancellation.
return this._cryptoStore.updateOutgoingRoomKeyRequest(
req.requestId, ROOM_KEY_REQUEST_STATES.SENT, {
@@ -181,7 +278,7 @@ export default class OutgoingRoomKeyRequestManager {
// the request cancelled. There is no point in
// sending another cancellation since the other tab
// will do it.
console.log(
logger.log(
'Tried to cancel room key request for ' +
stringifyRequestBody(requestBody) +
' but it was already cancelled in another tab',
@@ -201,20 +298,50 @@ export default class OutgoingRoomKeyRequestManager {
this._sendOutgoingRoomKeyRequestCancellation(
updatedReq,
).catch((e) => {
console.error(
logger.error(
"Error sending room key request cancellation;"
+ " will retry later.", e,
);
this._startTimer();
}).done();
});
});
}
default:
throw new Error('unhandled state: ' + req.state);
}
});
}
/**
* Look for room key requests by target device and state
*
* @param {string} userId Target user ID
* @param {string} deviceId Target device ID
*
* @return {Promise} resolves to a list of all the
* {@link module:crypto/store/base~OutgoingRoomKeyRequest}
*/
getOutgoingSentRoomKeyRequest(userId, deviceId) {
return this._cryptoStore.getOutgoingRoomKeyRequestsByTarget(
userId, deviceId, [ROOM_KEY_REQUEST_STATES.SENT],
);
}
/**
* Find anything in `sent` state, and kick it around the loop again.
* This is intended for situations where something substantial has changed, and we
* don't really expect the other end to even care about the cancellation.
* For example, after initialization or self-verification.
* @return {Promise} An array of `queueRoomKeyRequest` outputs.
*/
async cancelAndResendAllOutgoingRequests() {
const outgoings = await this._cryptoStore.getAllOutgoingRoomKeyRequestsByState(
ROOM_KEY_REQUEST_STATES.SENT,
);
return Promise.all(outgoings.map(({ requestBody, recipients }) =>
this.queueRoomKeyRequest(requestBody, recipients, true)));
}
// start the background timer to send queued requests, if the timer isn't
// already running
_startTimer() {
@@ -233,10 +360,10 @@ export default class OutgoingRoomKeyRequestManager {
}).catch((e) => {
// this should only happen if there is an indexeddb error,
// in which case we're a bit stuffed anyway.
console.warn(
logger.warn(
`error in OutgoingRoomKeyRequestManager: ${e}`,
);
}).done();
});
};
this._sendOutgoingRoomKeyRequestsTimer = global.setTimeout(
@@ -254,39 +381,42 @@ export default class OutgoingRoomKeyRequestManager {
return Promise.resolve();
}
console.log("Looking for queued outgoing room key requests");
return this._cryptoStore.getOutgoingRoomKeyRequestByState([
ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING,
ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING_AND_WILL_RESEND,
ROOM_KEY_REQUEST_STATES.UNSENT,
]).then((req) => {
if (!req) {
console.log("No more outgoing room key requests");
this._sendOutgoingRoomKeyRequestsTimer = null;
return;
}
let prom;
if (req.state === ROOM_KEY_REQUEST_STATES.UNSENT) {
prom = this._sendOutgoingRoomKeyRequest(req);
} else { // must be a cancellation
prom = this._sendOutgoingRoomKeyRequestCancellation(req);
switch (req.state) {
case ROOM_KEY_REQUEST_STATES.UNSENT:
prom = this._sendOutgoingRoomKeyRequest(req);
break;
case ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING:
prom = this._sendOutgoingRoomKeyRequestCancellation(req);
break;
case ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING_AND_WILL_RESEND:
prom = this._sendOutgoingRoomKeyRequestCancellation(req, true);
break;
}
return prom.then(() => {
// go around the loop again
return this._sendOutgoingRoomKeyRequests();
}).catch((e) => {
console.error("Error sending room key request; will retry later.", e);
logger.error("Error sending room key request; will retry later.", e);
this._sendOutgoingRoomKeyRequestsTimer = null;
this._startTimer();
}).done();
});
});
}
// given a RoomKeyRequest, send it and update the request record
_sendOutgoingRoomKeyRequest(req) {
console.log(
logger.log(
`Requesting keys for ${stringifyRequestBody(req.requestBody)}` +
` from ${stringifyRecipientList(req.recipients)}` +
`(id ${req.requestId})`,
@@ -300,7 +430,7 @@ export default class OutgoingRoomKeyRequestManager {
};
return this._sendMessageToDevices(
requestMessage, req.recipients, req.requestId,
requestMessage, req.recipients, req.requestTxnId || req.requestId,
).then(() => {
return this._cryptoStore.updateOutgoingRoomKeyRequest(
req.requestId, ROOM_KEY_REQUEST_STATES.UNSENT,
@@ -309,9 +439,10 @@ export default class OutgoingRoomKeyRequestManager {
});
}
// given a RoomKeyRequest, cancel it and delete the request record
_sendOutgoingRoomKeyRequestCancellation(req) {
console.log(
// Given a RoomKeyRequest, cancel it and delete the request record unless
// andResend is set, in which case transition to UNSENT.
_sendOutgoingRoomKeyRequestCancellation(req, andResend) {
logger.log(
`Sending cancellation for key request for ` +
`${stringifyRequestBody(req.requestBody)} to ` +
`${stringifyRecipientList(req.recipients)} ` +
@@ -327,6 +458,14 @@ export default class OutgoingRoomKeyRequestManager {
return this._sendMessageToDevices(
requestMessage, req.recipients, req.cancellationTxnId,
).then(() => {
if (andResend) {
// We want to resend, so transition to UNSENT
return this._cryptoStore.updateOutgoingRoomKeyRequest(
req.requestId,
ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING_AND_WILL_RESEND,
{ state: ROOM_KEY_REQUEST_STATES.UNSENT },
);
}
return this._cryptoStore.deleteOutgoingRoomKeyRequest(
req.requestId, ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING,
);
+65
View File
@@ -0,0 +1,65 @@
/*
Copyright 2018, 2019 New Vector Ltd
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.
*/
/**
* @module crypto/RoomList
*
* Manages the list of encrypted rooms
*/
import {IndexedDBCryptoStore} from './store/indexeddb-crypto-store';
/**
* @alias module:crypto/RoomList
*/
export class RoomList {
constructor(cryptoStore) {
this._cryptoStore = cryptoStore;
// Object of roomId -> room e2e info object (body of the m.room.encryption event)
this._roomEncryption = {};
}
async init() {
await this._cryptoStore.doTxn(
'readwrite', [IndexedDBCryptoStore.STORE_ROOMS], (txn) => {
this._cryptoStore.getEndToEndRooms(txn, (result) => {
this._roomEncryption = result;
});
},
);
}
getRoomEncryption(roomId) {
return this._roomEncryption[roomId] || null;
}
isRoomEncrypted(roomId) {
return Boolean(this.getRoomEncryption(roomId));
}
async setRoomEncryption(roomId, roomInfo) {
// important that this happens before calling into the store
// as it prevents the Crypto::setRoomEncryption from calling
// this twice for consecutive m.room.encryption events
this._roomEncryption[roomId] = roomInfo;
await this._cryptoStore.doTxn(
'readwrite', [IndexedDBCryptoStore.STORE_ROOMS], (txn) => {
this._cryptoStore.storeEndToEndRoom(roomId, roomInfo, txn);
},
);
}
}
+571
View File
@@ -0,0 +1,571 @@
/*
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
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 {EventEmitter} from 'events';
import {logger} from '../logger';
import * as olmlib from './olmlib';
import {randomString} from '../randomstring';
import {encryptAES, decryptAES} from './aes';
import {encodeBase64} from "./olmlib";
export const SECRET_STORAGE_ALGORITHM_V1_AES
= "m.secret_storage.v1.aes-hmac-sha2";
const ZERO_STR = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
/**
* Implements Secure Secret Storage and Sharing (MSC1946)
* @module crypto/SecretStorage
*/
export class SecretStorage extends EventEmitter {
constructor(baseApis, cryptoCallbacks) {
super();
this._baseApis = baseApis;
this._cryptoCallbacks = cryptoCallbacks;
this._requests = {};
this._incomingRequests = {};
}
async getDefaultKeyId() {
const defaultKey = await this._baseApis.getAccountDataFromServer(
'm.secret_storage.default_key',
);
if (!defaultKey) return null;
return defaultKey.key;
}
setDefaultKeyId(keyId) {
return new Promise(async (resolve, reject) => {
const listener = (ev) => {
if (
ev.getType() === 'm.secret_storage.default_key' &&
ev.getContent().key === keyId
) {
this._baseApis.removeListener('accountData', listener);
resolve();
}
};
this._baseApis.on('accountData', listener);
try {
await this._baseApis.setAccountData(
'm.secret_storage.default_key',
{ key: keyId },
);
} catch (e) {
this._baseApis.removeListener('accountData', listener);
reject(e);
}
});
}
/**
* Add a key for encrypting secrets.
*
* @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.
* @param {string} [keyId] the ID of the key. If not given, a random
* ID will be generated.
*
* @return {string} the ID of the key
*/
async addKey(algorithm, opts, keyId) {
const keyData = {algorithm};
if (!opts) opts = {};
if (opts.name) {
keyData.name = opts.name;
}
if (algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) {
if (opts.passphrase) {
keyData.passphrase = opts.passphrase;
}
if (opts.key) {
const {iv, mac} = await SecretStorage._calculateKeyCheck(opts.key);
keyData.iv = iv;
keyData.mac = mac;
}
} else {
throw new Error(`Unknown key algorithm ${opts.algorithm}`);
}
if (!keyId) {
do {
keyId = randomString(32);
} while (
await this._baseApis.getAccountDataFromServer(
`m.secret_storage.key.${keyId}`,
)
);
}
await this._baseApis.setAccountData(
`m.secret_storage.key.${keyId}`, keyData,
);
return keyId;
}
/**
* Get the key information for 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.
* @returns {Array?} If the key was found, the return value is an array of
* the form [keyId, keyInfo]. Otherwise, null is returned.
*/
async getKey(keyId) {
if (!keyId) {
keyId = await this.getDefaultKeyId();
}
if (!keyId) {
return null;
}
const keyInfo = await this._baseApis.getAccountDataFromServer(
"m.secret_storage.key." + keyId,
);
return keyInfo ? [keyId, keyInfo] : null;
}
/**
* Check whether we have a key with a given ID.
*
* @param {string} [keyId = default key's ID] The ID of the key to check
* for. Defaults to the default key ID if not provided.
* @return {boolean} Whether we have the key.
*/
async hasKey(keyId) {
return !!(await this.getKey(keyId));
}
/**
* Check whether a key matches what we expect based on the key info
*
* @param {Uint8Array} key the key to check
* @param {object} info the key info
*
* @return {boolean} whether or not the key matches
*/
async checkKey(key, info) {
if (info.algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) {
if (info.mac) {
const {mac} = await SecretStorage._calculateKeyCheck(key, info.iv);
return info.mac.replace(/=+$/g, '') === mac.replace(/=+$/g, '');
} else {
// if we have no information, we have to assume the key is right
return true;
}
} else {
throw new Error("Unknown algorithm");
}
}
static async _calculateKeyCheck(key, iv) {
return await encryptAES(ZERO_STR, key, "", iv);
}
/**
* Store an encrypted secret on the server
*
* @param {string} name The name of the secret
* @param {string} secret The secret contents.
* @param {Array} keys The IDs of the keys to use to encrypt the secret
* or null/undefined to use the default key.
*/
async store(name, secret, keys) {
const encrypted = {};
if (!keys) {
const defaultKeyId = await this.getDefaultKeyId();
if (!defaultKeyId) {
throw new Error("No keys specified and no default key present");
}
keys = [defaultKeyId];
}
if (keys.length === 0) {
throw new Error("Zero keys given to encrypt with!");
}
for (const keyId of keys) {
// get key information from key storage
const keyInfo = await this._baseApis.getAccountDataFromServer(
"m.secret_storage.key." + keyId,
);
if (!keyInfo) {
throw new Error("Unknown key: " + keyId);
}
// encrypt secret, based on the algorithm
if (keyInfo.algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) {
const keys = {[keyId]: keyInfo};
const [, encryption] = await this._getSecretStorageKey(keys, name);
encrypted[keyId] = await encryption.encrypt(secret);
} else {
logger.warn("unknown algorithm for secret storage key " + keyId
+ ": " + keyInfo.algorithm);
// do nothing if we don't understand the encryption algorithm
}
}
// save encrypted secret
await this._baseApis.setAccountData(name, {encrypted});
}
/**
* Temporary method to fix up existing accounts where secrets
* are incorrectly stored without the 'encrypted' level
*
* @param {string} name The name of the secret
* @param {object} secretInfo The account data object
* @returns {object} The fixed object or null if no fix was performed
*/
async _fixupStoredSecret(name, secretInfo) {
// We assume the secret was only stored passthrough for 1
// key - this was all the broken code supported.
const keys = Object.keys(secretInfo);
if (
keys.length === 1 && keys[0] !== 'encrypted' &&
secretInfo[keys[0]].passthrough
) {
const hasKey = await this.hasKey(keys[0]);
if (hasKey) {
console.log("Fixing up passthrough secret: " + name);
await this.storePassthrough(name, keys[0]);
const newData = await this._baseApis.getAccountDataFromServer(name);
return newData;
}
}
return null;
}
/**
* Get a secret from storage.
*
* @param {string} name the name of the secret
*
* @return {string} the contents of the secret
*/
async get(name) {
let secretInfo = await this._baseApis.getAccountDataFromServer(name);
if (!secretInfo) {
return;
}
if (!secretInfo.encrypted) {
// try to fix it up
secretInfo = await this._fixupStoredSecret(name, secretInfo);
if (!secretInfo || !secretInfo.encrypted) {
throw new Error("Content is not encrypted!");
}
}
// get possible keys to decrypt
const keys = {};
for (const keyId of Object.keys(secretInfo.encrypted)) {
// get key information from key storage
const keyInfo = await this._baseApis.getAccountDataFromServer(
"m.secret_storage.key." + keyId,
);
const encInfo = secretInfo.encrypted[keyId];
// only use keys we understand the encryption algorithm of
if (keyInfo.algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) {
if (encInfo.iv && encInfo.ciphertext && encInfo.mac) {
keys[keyId] = keyInfo;
}
}
}
let keyId;
let decryption;
try {
// fetch private key from app
[keyId, decryption] = await this._getSecretStorageKey(keys, name);
const encInfo = secretInfo.encrypted[keyId];
// We don't actually need the decryption object if it's a passthrough
// since we just want to return the key itself. It must be base64
// encoded, since this is how a key would normally be stored.
if (encInfo.passthrough) return encodeBase64(decryption.get_private_key());
return await decryption.decrypt(encInfo);
} finally {
if (decryption && decryption.free) decryption.free();
}
}
/**
* Check if a secret is stored on the server.
*
* @param {string} name the name of the secret
* @param {boolean} checkKey check if the secret is encrypted by a trusted key
*
* @return {object?} map of key name to key info the secret is encrypted
* with, or null if it is not present or not encrypted with a trusted
* key
*/
async isStored(name, checkKey) {
// check if secret exists
let secretInfo = await this._baseApis.getAccountDataFromServer(name);
if (!secretInfo) return null;
if (!secretInfo.encrypted) {
// try to fix it up
secretInfo = await this._fixupStoredSecret(name, secretInfo);
if (!secretInfo || !secretInfo.encrypted) {
return null;
}
}
if (checkKey === undefined) checkKey = true;
const ret = {};
// filter secret encryption keys with supported algorithm
for (const keyId of Object.keys(secretInfo.encrypted)) {
// get key information from key storage
const keyInfo = await this._baseApis.getAccountDataFromServer(
"m.secret_storage.key." + keyId,
);
if (!keyInfo) continue;
const encInfo = secretInfo.encrypted[keyId];
// only use keys we understand the encryption algorithm of
if (keyInfo.algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) {
if (encInfo.iv && encInfo.ciphertext && encInfo.mac) {
ret[keyId] = keyInfo;
}
}
}
return Object.keys(ret).length ? ret : null;
}
/**
* Request a secret from another device
*
* @param {string} name the name of the secret to request
* @param {string[]} devices the devices to request the secret from
*
* @return {string} the contents of the secret
*/
request(name, devices) {
const requestId = this._baseApis.makeTxnId();
const requestControl = this._requests[requestId] = {
devices,
};
const promise = new Promise((resolve, reject) => {
requestControl.resolve = resolve;
requestControl.reject = reject;
});
const cancel = (reason) => {
// send cancellation event
const cancelData = {
action: "request_cancellation",
requesting_device_id: this._baseApis.deviceId,
request_id: requestId,
};
const toDevice = {};
for (const device of devices) {
toDevice[device] = cancelData;
}
this._baseApis.sendToDevice("m.secret.request", {
[this._baseApis.getUserId()]: toDevice,
});
// and reject the promise so that anyone waiting on it will be
// notified
requestControl.reject(new Error(reason || "Cancelled"));
};
// send request to devices
const requestData = {
name,
action: "request",
requesting_device_id: this._baseApis.deviceId,
request_id: requestId,
};
const toDevice = {};
for (const device of devices) {
toDevice[device] = requestData;
}
logger.info(`Request secret ${name} from ${devices}, id ${requestId}`);
this._baseApis.sendToDevice("m.secret.request", {
[this._baseApis.getUserId()]: toDevice,
});
return {
request_id: requestId,
promise,
cancel,
};
}
async _onRequestReceived(event) {
const sender = event.getSender();
const content = event.getContent();
if (sender !== this._baseApis.getUserId()
|| !(content.name && content.action
&& content.requesting_device_id && content.request_id)) {
// ignore requests from anyone else, for now
return;
}
const deviceId = content.requesting_device_id;
// check if it's a cancel
if (content.action === "request_cancellation") {
if (this._incomingRequests[deviceId]
&& this._incomingRequests[deviceId][content.request_id]) {
logger.info("received request cancellation for secret (" + sender
+ ", " + deviceId + ", " + content.request_id + ")");
this.baseApis.emit("crypto.secrets.requestCancelled", {
user_id: sender,
device_id: deviceId,
request_id: content.request_id,
});
}
} else if (content.action === "request") {
if (deviceId === this._baseApis.deviceId) {
// no point in trying to send ourself the secret
return;
}
// check if we have the secret
logger.info("received request for secret (" + sender
+ ", " + deviceId + ", " + content.request_id + ")");
if (!this._cryptoCallbacks.onSecretRequested) {
return;
}
const secret = await this._cryptoCallbacks.onSecretRequested({
user_id: sender,
device_id: deviceId,
request_id: content.request_id,
name: content.name,
device_trust: this._baseApis.checkDeviceTrust(sender, deviceId),
});
if (secret) {
logger.info(`Preparing ${content.name} secret for ${deviceId}`);
const payload = {
type: "m.secret.send",
content: {
request_id: content.request_id,
secret: secret,
},
};
const encryptedContent = {
algorithm: olmlib.OLM_ALGORITHM,
sender_key: this._baseApis._crypto._olmDevice.deviceCurve25519Key,
ciphertext: {},
};
await olmlib.ensureOlmSessionsForDevices(
this._baseApis._crypto._olmDevice,
this._baseApis,
{
[sender]: [
this._baseApis.getStoredDevice(sender, deviceId),
],
},
);
await olmlib.encryptMessageForDevice(
encryptedContent.ciphertext,
this._baseApis.getUserId(),
this._baseApis.deviceId,
this._baseApis._crypto._olmDevice,
sender,
this._baseApis.getStoredDevice(sender, deviceId),
payload,
);
const contentMap = {
[sender]: {
[deviceId]: encryptedContent,
},
};
logger.info(`Sending ${content.name} secret for ${deviceId}`);
this._baseApis.sendToDevice("m.room.encrypted", contentMap);
} else {
logger.info(`Request denied for ${content.name} secret for ${deviceId}`);
}
}
}
_onSecretReceived(event) {
if (event.getSender() !== this._baseApis.getUserId()) {
// we shouldn't be receiving secrets from anyone else, so ignore
// because someone could be trying to send us bogus data
return;
}
const content = event.getContent();
logger.log("got secret share for request", content.request_id);
const requestControl = this._requests[content.request_id];
if (requestControl) {
// make sure that the device that sent it is one of the devices that
// we requested from
const deviceInfo = this._baseApis._crypto._deviceList.getDeviceByIdentityKey(
olmlib.OLM_ALGORITHM,
event.getSenderKey(),
);
if (!deviceInfo) {
logger.log(
"secret share from unknown device with key", event.getSenderKey(),
);
return;
}
if (!requestControl.devices.includes(deviceInfo.deviceId)) {
logger.log("unsolicited secret share from device", deviceInfo.deviceId);
return;
}
requestControl.resolve(content.secret);
}
}
async _getSecretStorageKey(keys, name) {
if (!this._cryptoCallbacks.getSecretStorageKey) {
throw new Error("No getSecretStorageKey callback supplied");
}
const returned = await this._cryptoCallbacks.getSecretStorageKey({ keys }, name);
if (!returned) {
throw new Error("getSecretStorageKey callback returned falsey");
}
if (returned.length < 2) {
throw new Error("getSecretStorageKey callback returned invalid data");
}
const [keyId, privateKey] = returned;
if (!keys[keyId]) {
throw new Error("App returned unknown key from getSecretStorageKey!");
}
if (keys[keyId].algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) {
const decryption = {
encrypt: async function(secret) {
return await encryptAES(secret, privateKey, name);
},
decrypt: async function(encInfo) {
return await decryptAES(encInfo, privateKey, name);
},
};
return [keyId, decryption];
} else {
throw new Error("Unknown key type: " + keys[keyId].algorithm);
}
}
}
+251
View File
@@ -0,0 +1,251 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
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 {getCrypto} from '../utils';
import {decodeBase64, encodeBase64} from './olmlib';
const subtleCrypto = (typeof window !== "undefined" && window.crypto) ?
(window.crypto.subtle || window.crypto.webkitSubtle) : null;
// salt for HKDF, with 8 bytes of zeros
const zerosalt = new Uint8Array(8);
/**
* encrypt a string in Node.js
*
* @param {string} data the plaintext to encrypt
* @param {Uint8Array} key the encryption key to use
* @param {string} name the name of the secret
* @param {string} ivStr the initialization vector to use
*/
async function encryptNode(data, key, name, ivStr) {
const crypto = getCrypto();
if (!crypto) {
throw new Error("No usable crypto implementation");
}
let iv;
if (ivStr) {
iv = decodeBase64(ivStr);
} else {
iv = crypto.randomBytes(16);
}
// clear bit 63 of the IV to stop us hitting the 64-bit counter boundary
// (which would mean we wouldn't be able to decrypt on Android). The loss
// of a single bit of iv is a price we have to pay.
iv[8] &= 0x7f;
const [aesKey, hmacKey] = deriveKeysNode(key, name);
const cipher = crypto.createCipheriv("aes-256-ctr", aesKey, iv);
const ciphertext = cipher.update(data, "utf-8", "base64")
+ cipher.final("base64");
const hmac = crypto.createHmac("sha256", hmacKey)
.update(ciphertext, "base64").digest("base64");
return {
iv: encodeBase64(iv),
ciphertext: ciphertext,
mac: hmac,
};
}
/**
* decrypt a string in Node.js
*
* @param {object} data the encrypted data
* @param {string} data.ciphertext the ciphertext in base64
* @param {string} data.iv the initialization vector in base64
* @param {string} data.mac the HMAC in base64
* @param {Uint8Array} key the encryption key to use
* @param {string} name the name of the secret
*/
async function decryptNode(data, key, name) {
const crypto = getCrypto();
if (!crypto) {
throw new Error("No usable crypto implementation");
}
const [aesKey, hmacKey] = deriveKeysNode(key, name);
const hmac = crypto.createHmac("sha256", hmacKey)
.update(data.ciphertext, "base64").digest("base64").replace(/=+$/g, '');
if (hmac !== data.mac.replace(/=+$/g, '')) {
throw new Error(`Error decrypting secret ${name}: bad MAC`);
}
const decipher = crypto.createDecipheriv(
"aes-256-ctr", aesKey, decodeBase64(data.iv),
);
return decipher.update(data.ciphertext, "base64", "utf-8")
+ decipher.final("utf-8");
}
function deriveKeysNode(key, name) {
const crypto = getCrypto();
const prk = crypto.createHmac("sha256", zerosalt)
.update(key).digest();
const b = Buffer.alloc(1, 1);
const aesKey = crypto.createHmac("sha256", prk)
.update(name, "utf-8").update(b).digest();
b[0] = 2;
const hmacKey = crypto.createHmac("sha256", prk)
.update(aesKey).update(name, "utf-8").update(b).digest();
return [aesKey, hmacKey];
}
/**
* encrypt a string in Node.js
*
* @param {string} data the plaintext to encrypt
* @param {Uint8Array} key the encryption key to use
* @param {string} name the name of the secret
* @param {string} ivStr the initialization vector to use
*/
async function encryptBrowser(data, key, name, ivStr) {
let iv;
if (ivStr) {
iv = decodeBase64(ivStr);
} else {
iv = new Uint8Array(16);
window.crypto.getRandomValues(iv);
}
// clear bit 63 of the IV to stop us hitting the 64-bit counter boundary
// (which would mean we wouldn't be able to decrypt on Android). The loss
// of a single bit of iv is a price we have to pay.
iv[8] &= 0x7f;
const [aesKey, hmacKey] = await deriveKeysBrowser(key, name);
const encodedData = new TextEncoder().encode(data);
const ciphertext = await subtleCrypto.encrypt(
{
name: "AES-CTR",
counter: iv,
length: 64,
},
aesKey,
encodedData,
);
const hmac = await subtleCrypto.sign(
{name: 'HMAC'},
hmacKey,
ciphertext,
);
return {
iv: encodeBase64(iv),
ciphertext: encodeBase64(ciphertext),
mac: encodeBase64(hmac),
};
}
/**
* decrypt a string in the browser
*
* @param {object} data the encrypted data
* @param {string} data.ciphertext the ciphertext in base64
* @param {string} data.iv the initialization vector in base64
* @param {string} data.mac the HMAC in base64
* @param {Uint8Array} key the encryption key to use
* @param {string} name the name of the secret
*/
async function decryptBrowser(data, key, name) {
const [aesKey, hmacKey] = await deriveKeysBrowser(key, name);
const ciphertext = decodeBase64(data.ciphertext);
if (!await subtleCrypto.verify(
{name: "HMAC"},
hmacKey,
decodeBase64(data.mac),
ciphertext,
)) {
throw new Error(`Error decrypting secret ${name}: bad MAC`);
}
const plaintext = await subtleCrypto.decrypt(
{
name: "AES-CTR",
counter: decodeBase64(data.iv),
length: 64,
},
aesKey,
ciphertext,
);
return new TextDecoder().decode(new Uint8Array(plaintext));
}
async function deriveKeysBrowser(key, name) {
const hkdfkey = await subtleCrypto.importKey(
'raw',
key,
{name: "HKDF"},
false,
["deriveBits"],
);
const keybits = await subtleCrypto.deriveBits(
{
name: "HKDF",
salt: zerosalt,
info: (new TextEncoder().encode(name)),
hash: "SHA-256",
},
hkdfkey,
512,
);
const aesKey = keybits.slice(0, 32);
const hmacKey = keybits.slice(32);
const aesProm = subtleCrypto.importKey(
'raw',
aesKey,
{name: 'AES-CTR'},
false,
['encrypt', 'decrypt'],
);
const hmacProm = subtleCrypto.importKey(
'raw',
hmacKey,
{
name: 'HMAC',
hash: {name: 'SHA-256'},
},
false,
['sign', 'verify'],
);
return await Promise.all([aesProm, hmacProm]);
}
export function encryptAES(...args) {
return subtleCrypto ? encryptBrowser(...args) : encryptNode(...args);
}
export function decryptAES(...args) {
return subtleCrypto ? decryptBrowser(...args) : decryptNode(...args);
}
+41 -31
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
@@ -52,7 +50,7 @@ export const DECRYPTION_CLASSES = {};
* @param {string} params.roomId The ID of the room we will be sending to
* @param {object} params.config The body of the m.room.encryption event
*/
class EncryptionAlgorithm {
export class EncryptionAlgorithm {
constructor(params) {
this._userId = params.userId;
this._deviceId = params.deviceId;
@@ -62,6 +60,15 @@ class EncryptionAlgorithm {
this._roomId = params.roomId;
}
/**
* Perform any background tasks that can be done before a message is ready to
* send, in order to speed up sending of the message.
*
* @param {module:models/room} room the room the event is in
*/
prepareToEncrypt(room) {
}
/**
* Encrypt a message event
*
@@ -72,7 +79,7 @@ class EncryptionAlgorithm {
* @param {string} eventType
* @param {object} plaintext event content
*
* @return {module:client.Promise} Promise which resolves to the new event body
* @return {Promise} Promise which resolves to the new event body
*/
/**
@@ -86,7 +93,6 @@ class EncryptionAlgorithm {
onRoomMembership(event, member, oldMembership) {
}
}
export {EncryptionAlgorithm}; // https://github.com/jsdoc3/jsdoc/issues/1272
/**
* base type for decryption implementations
@@ -100,7 +106,7 @@ export {EncryptionAlgorithm}; // https://github.com/jsdoc3/jsdoc/issues/1272
* @param {string=} params.roomId The ID of the room we will be receiving
* from. Null for to-device events.
*/
class DecryptionAlgorithm {
export class DecryptionAlgorithm {
constructor(params) {
this._userId = params.userId;
this._crypto = params.crypto;
@@ -161,8 +167,17 @@ class DecryptionAlgorithm {
shareKeysWithDevice(keyRequest) {
throw new Error("shareKeysWithDevice not supported for this DecryptionAlgorithm");
}
/**
* Retry decrypting all the events from a sender that haven't been
* decrypted yet.
*
* @param {string} senderKey the sender's key
*/
async retryDecryptionFromSender(senderKey) {
// ignore by default
}
}
export {DecryptionAlgorithm}; // https://github.com/jsdoc3/jsdoc/issues/1272
/**
* Exception thrown when decryption fails
@@ -175,34 +190,29 @@ export {DecryptionAlgorithm}; // https://github.com/jsdoc3/jsdoc/issues/1272
*
* @extends Error
*/
class DecryptionError extends Error {
constructor(msg, details) {
export class DecryptionError extends Error {
constructor(code, msg, details) {
super(msg);
this.code = code;
this.name = 'DecryptionError';
this.details = details;
}
/**
* override the string used when logging
*
* @returns {String}
*/
toString() {
let result = this.name + '[msg: ' + this.message;
if (this.details) {
result += ', ' +
Object.keys(this.details).map(
(k) => k + ': ' + this.details[k],
).join(', ');
}
result += ']';
return result;
this.detailedString = _detailedStringForDecryptionError(this, details);
}
}
export {DecryptionError}; // https://github.com/jsdoc3/jsdoc/issues/1272
function _detailedStringForDecryptionError(err, details) {
let result = err.name + '[msg: ' + err.message;
if (details) {
result += ', ' +
Object.keys(details).map(
(k) => k + ': ' + details[k],
).join(', ');
}
result += ']';
return result;
}
/**
* Exception thrown specifically when we want to warn the user to consider
+4 -19
View File
@@ -1,5 +1,6 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,28 +14,12 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
"use strict";
/**
* @module crypto/algorithms
*/
const base = require("./base");
import "./olm";
import "./megolm";
require("./olm");
require("./megolm");
/**
* @see module:crypto/algorithms/base.ENCRYPTION_CLASSES
*/
module.exports.ENCRYPTION_CLASSES = base.ENCRYPTION_CLASSES;
/**
* @see module:crypto/algorithms/base.DECRYPTION_CLASSES
*/
module.exports.DECRYPTION_CLASSES = base.DECRYPTION_CLASSES;
/**
* @see module:crypto/algorithms/base.DecryptionError
*/
module.exports.DecryptionError = base.DecryptionError;
export * from "./base";
File diff suppressed because it is too large Load Diff
+102 -66
View File
@@ -13,44 +13,48 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
"use strict";
/**
* Defines m.olm encryption/decryption
*
* @module crypto/algorithms/olm
*/
import Promise from 'bluebird';
const utils = require("../../utils");
const olmlib = require("../olmlib");
const DeviceInfo = require("../deviceinfo");
import {logger} from '../../logger';
import * as utils from "../../utils";
import {polyfillSuper} from "../../utils";
import * as olmlib from "../olmlib";
import {DeviceInfo} from "../deviceinfo";
import {
DecryptionAlgorithm,
DecryptionError,
EncryptionAlgorithm,
registerAlgorithm,
} from "./base";
const DeviceVerification = DeviceInfo.DeviceVerification;
const base = require("./base");
/**
* Olm encryption implementation
*
* @constructor
* @extends {module:crypto/algorithms/base.EncryptionAlgorithm}
* @extends {module:crypto/algorithms/EncryptionAlgorithm}
*
* @param {object} params parameters, as per
* {@link module:crypto/algorithms/base.EncryptionAlgorithm}
* {@link module:crypto/algorithms/EncryptionAlgorithm}
*/
function OlmEncryption(params) {
base.EncryptionAlgorithm.call(this, params);
polyfillSuper(this, EncryptionAlgorithm, params);
this._sessionPrepared = false;
this._prepPromise = null;
}
utils.inherits(OlmEncryption, base.EncryptionAlgorithm);
utils.inherits(OlmEncryption, EncryptionAlgorithm);
/**
* @private
* @param {string[]} roomMembers list of currently-joined users in the room
* @return {module:client.Promise} Promise which resolves when setup is complete
* @return {Promise} Promise which resolves when setup is complete
*/
OlmEncryption.prototype._ensureSession = function(roomMembers) {
if (this._prepPromise) {
@@ -81,76 +85,78 @@ OlmEncryption.prototype._ensureSession = function(roomMembers) {
* @param {string} eventType
* @param {object} content plaintext event content
*
* @return {module:client.Promise} Promise which resolves to the new event body
* @return {Promise} Promise which resolves to the new event body
*/
OlmEncryption.prototype.encryptMessage = function(room, eventType, content) {
OlmEncryption.prototype.encryptMessage = async function(room, eventType, content) {
// pick the list of recipients based on the membership list.
//
// TODO: there is a race condition here! What if a new user turns up
// just as you are sending a secret message?
const users = utils.map(room.getJoinedMembers(), function(u) {
const members = await room.getEncryptionTargetMembers();
const users = utils.map(members, function(u) {
return u.userId;
});
const self = this;
return this._ensureSession(users).then(function() {
const payloadFields = {
room_id: room.roomId,
type: eventType,
content: content,
};
await this._ensureSession(users);
const encryptedContent = {
algorithm: olmlib.OLM_ALGORITHM,
sender_key: self._olmDevice.deviceCurve25519Key,
ciphertext: {},
};
const payloadFields = {
room_id: room.roomId,
type: eventType,
content: content,
};
const promises = [];
const encryptedContent = {
algorithm: olmlib.OLM_ALGORITHM,
sender_key: self._olmDevice.deviceCurve25519Key,
ciphertext: {},
};
for (let i = 0; i < users.length; ++i) {
const userId = users[i];
const devices = self._crypto.getStoredDevicesForUser(userId);
const promises = [];
for (let j = 0; j < devices.length; ++j) {
const deviceInfo = devices[j];
const key = deviceInfo.getIdentityKey();
if (key == self._olmDevice.deviceCurve25519Key) {
// don't bother sending to ourself
continue;
}
if (deviceInfo.verified == DeviceVerification.BLOCKED) {
// don't bother setting up sessions with blocked users
continue;
}
for (let i = 0; i < users.length; ++i) {
const userId = users[i];
const devices = self._crypto.getStoredDevicesForUser(userId);
promises.push(
olmlib.encryptMessageForDevice(
encryptedContent.ciphertext,
self._userId, self._deviceId, self._olmDevice,
userId, deviceInfo, payloadFields,
),
);
for (let j = 0; j < devices.length; ++j) {
const deviceInfo = devices[j];
const key = deviceInfo.getIdentityKey();
if (key == self._olmDevice.deviceCurve25519Key) {
// don't bother sending to ourself
continue;
}
if (deviceInfo.verified == DeviceVerification.BLOCKED) {
// don't bother setting up sessions with blocked users
continue;
}
}
return Promise.all(promises).return(encryptedContent);
});
promises.push(
olmlib.encryptMessageForDevice(
encryptedContent.ciphertext,
self._userId, self._deviceId, self._olmDevice,
userId, deviceInfo, payloadFields,
),
);
}
}
return await Promise.all(promises).then(() => encryptedContent);
};
/**
* Olm decryption implementation
*
* @constructor
* @extends {module:crypto/algorithms/base.DecryptionAlgorithm}
* @extends {module:crypto/algorithms/DecryptionAlgorithm}
* @param {object} params parameters, as per
* {@link module:crypto/algorithms/base.DecryptionAlgorithm}
* {@link module:crypto/algorithms/DecryptionAlgorithm}
*/
function OlmDecryption(params) {
base.DecryptionAlgorithm.call(this, params);
polyfillSuper(this, DecryptionAlgorithm, params);
}
utils.inherits(OlmDecryption, base.DecryptionAlgorithm);
utils.inherits(OlmDecryption, DecryptionAlgorithm);
/**
* @inheritdoc
@@ -168,11 +174,17 @@ OlmDecryption.prototype.decryptEvent = async function(event) {
const ciphertext = content.ciphertext;
if (!ciphertext) {
throw new base.DecryptionError("Missing ciphertext");
throw new DecryptionError(
"OLM_MISSING_CIPHERTEXT",
"Missing ciphertext",
);
}
if (!(this._olmDevice.deviceCurve25519Key in ciphertext)) {
throw new base.DecryptionError("Not included in recipients");
throw new DecryptionError(
"OLM_NOT_INCLUDED_IN_RECIPIENTS",
"Not included in recipients",
);
}
const message = ciphertext[this._olmDevice.deviceCurve25519Key];
let payloadString;
@@ -180,7 +192,8 @@ OlmDecryption.prototype.decryptEvent = async function(event) {
try {
payloadString = await this._decryptMessage(deviceKey, message);
} catch (e) {
throw new base.DecryptionError(
throw new DecryptionError(
"OLM_BAD_ENCRYPTED_MESSAGE",
"Bad Encrypted Message", {
sender: deviceKey,
err: e,
@@ -193,13 +206,15 @@ OlmDecryption.prototype.decryptEvent = async function(event) {
// check that we were the intended recipient, to avoid unknown-key attack
// https://github.com/vector-im/vector-web/issues/2483
if (payload.recipient != this._userId) {
throw new base.DecryptionError(
throw new DecryptionError(
"OLM_BAD_RECIPIENT",
"Message was intented for " + payload.recipient,
);
}
if (payload.recipient_keys.ed25519 != this._olmDevice.deviceEd25519Key) {
throw new base.DecryptionError(
throw new DecryptionError(
"OLM_BAD_RECIPIENT_KEY",
"Message not intended for this device", {
intended: payload.recipient_keys.ed25519,
our_key: this._olmDevice.deviceEd25519Key,
@@ -212,7 +227,8 @@ OlmDecryption.prototype.decryptEvent = async function(event) {
// (this check is also provided via the sender's embedded ed25519 key,
// which is checked elsewhere).
if (payload.sender != event.getSender()) {
throw new base.DecryptionError(
throw new DecryptionError(
"OLM_FORWARDED_MESSAGE",
"Message forwarded from " + payload.sender, {
reported_sender: event.getSender(),
},
@@ -221,7 +237,8 @@ OlmDecryption.prototype.decryptEvent = async function(event) {
// Olm events intended for a room have a room_id.
if (payload.room_id !== event.getRoomId()) {
throw new base.DecryptionError(
throw new DecryptionError(
"OLM_BAD_ROOM",
"Message intended for room " + payload.room_id, {
reported_room: event.room_id,
},
@@ -247,6 +264,25 @@ OlmDecryption.prototype.decryptEvent = async function(event) {
*/
OlmDecryption.prototype._decryptMessage = async function(
theirDeviceIdentityKey, message,
) {
// This is a wrapper that serialises decryptions of prekey messages, because
// otherwise we race between deciding we have no active sessions for the message
// and creating a new one, which we can only do once because it removes the OTK.
if (message.type !== 0) {
// not a prekey message: we can safely just try & decrypt it
return this._reallyDecryptMessage(theirDeviceIdentityKey, message);
} else {
const myPromise = this._olmDevice._olmPrekeyPromise.then(() => {
return this._reallyDecryptMessage(theirDeviceIdentityKey, message);
});
// we want the error, but don't propagate it to the next decryption
this._olmDevice._olmPrekeyPromise = myPromise.catch(() => {});
return await myPromise;
}
};
OlmDecryption.prototype._reallyDecryptMessage = async function(
theirDeviceIdentityKey, message,
) {
const sessionIds = await this._olmDevice.getSessionIdsForDevice(
theirDeviceIdentityKey,
@@ -260,7 +296,7 @@ OlmDecryption.prototype._decryptMessage = async function(
const payload = await this._olmDevice.decryptMessage(
theirDeviceIdentityKey, sessionId, message.type, message.body,
);
console.log(
logger.log(
"Decrypted Olm message from " + theirDeviceIdentityKey +
" with session " + sessionId,
);
@@ -315,7 +351,7 @@ OlmDecryption.prototype._decryptMessage = async function(
);
}
console.log(
logger.log(
"created new inbound Olm session ID " +
res.session_id + " with " + theirDeviceIdentityKey,
);
@@ -323,4 +359,4 @@ OlmDecryption.prototype._decryptMessage = async function(
};
base.registerAlgorithm(olmlib.OLM_ALGORITHM, OlmEncryption, OlmDecryption);
registerAlgorithm(olmlib.OLM_ALGORITHM, OlmEncryption, OlmDecryption);
+4 -5
View File
@@ -1,5 +1,6 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,8 +14,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
"use strict";
/**
* @module crypto/deviceinfo
@@ -44,7 +43,7 @@ limitations under the License.
*
* @param {string} deviceId id of the device
*/
function DeviceInfo(deviceId) {
export function DeviceInfo(deviceId) {
// you can't change the deviceId
Object.defineProperty(this, 'deviceId', {
enumerable: true,
@@ -56,6 +55,7 @@ function DeviceInfo(deviceId) {
this.verified = DeviceVerification.UNVERIFIED;
this.known = false;
this.unsigned = {};
this.signatures = {};
}
/**
@@ -88,6 +88,7 @@ DeviceInfo.prototype.toStorage = function() {
verified: this.verified,
known: this.known,
unsigned: this.unsigned,
signatures: this.signatures,
};
};
@@ -165,5 +166,3 @@ DeviceInfo.DeviceVerification = {
const DeviceVerification = DeviceInfo.DeviceVerification;
/** */
module.exports = DeviceInfo;
+2625 -227
View File
File diff suppressed because it is too large Load Diff
+83
View File
@@ -0,0 +1,83 @@
/*
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';
const DEFAULT_ITERATIONS = 500000;
const DEFAULT_BITSIZE = 256;
export async function keyFromAuthData(authData, password) {
if (!global.Olm) {
throw new Error("Olm is not available");
}
if (!authData.private_key_salt || !authData.private_key_iterations) {
throw new Error(
"Salt and/or iterations not found: " +
"this backup cannot be restored with a passphrase",
);
}
return await deriveKey(
password, authData.private_key_salt,
authData.private_key_iterations,
authData.private_key_bits || DEFAULT_BITSIZE,
);
}
export async function keyFromPassphrase(password) {
if (!global.Olm) {
throw new Error("Olm is not available");
}
const salt = randomString(32);
const key = await deriveKey(password, salt, DEFAULT_ITERATIONS, DEFAULT_BITSIZE);
return { key, salt, iterations: DEFAULT_ITERATIONS };
}
export async function deriveKey(password, salt, iterations, numBits = DEFAULT_BITSIZE) {
const subtleCrypto = global.crypto.subtle;
const TextEncoder = global.TextEncoder;
if (!subtleCrypto || !TextEncoder) {
// TODO: Implement this for node
throw new Error("Password-based backup is not avaiable on this platform");
}
const key = await subtleCrypto.importKey(
'raw',
new TextEncoder().encode(password),
{name: 'PBKDF2'},
false,
['deriveBits'],
);
const keybits = await subtleCrypto.deriveBits(
{
name: 'PBKDF2',
salt: new TextEncoder().encode(salt),
iterations: iterations,
hash: 'SHA-512',
},
key,
numBits,
);
return new Uint8Array(keybits);
}
+285 -53
View File
@@ -1,5 +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.
@@ -20,20 +22,24 @@ limitations under the License.
* Utilities common to olm encryption algorithms
*/
import Promise from 'bluebird';
const anotherjson = require('another-json');
const utils = require("../utils");
import {logger} from '../logger';
import * as utils from "../utils";
import anotherjson from "another-json";
/**
* matrix algorithm tag for olm
*/
module.exports.OLM_ALGORITHM = "m.olm.v1.curve25519-aes-sha2";
export const OLM_ALGORITHM = "m.olm.v1.curve25519-aes-sha2";
/**
* matrix algorithm tag for megolm
*/
module.exports.MEGOLM_ALGORITHM = "m.megolm.v1.aes-sha2";
export const MEGOLM_ALGORITHM = "m.megolm.v1.aes-sha2";
/**
* matrix algorithm tag for megolm backups
*/
export const MEGOLM_BACKUP_ALGORITHM = "m.megolm_backup.v1.curve25519-aes-sha2";
/**
@@ -52,7 +58,7 @@ module.exports.MEGOLM_ALGORITHM = "m.megolm.v1.aes-sha2";
* Returns a promise which resolves (to undefined) when the payload
* has been encrypted into `resultsObject`
*/
module.exports.encryptMessageForDevice = async function(
export async function encryptMessageForDevice(
resultsObject,
ourUserId, ourDeviceId, olmDevice, recipientUserId, recipientDevice,
payloadFields,
@@ -65,7 +71,7 @@ module.exports.encryptMessageForDevice = async function(
return;
}
console.log(
logger.log(
"Using sessionid " + sessionId + " for device " +
recipientUserId + ":" + recipientDevice.deviceId,
);
@@ -105,7 +111,58 @@ module.exports.encryptMessageForDevice = async function(
resultsObject[deviceKey] = await olmDevice.encryptMessage(
deviceKey, sessionId, JSON.stringify(payload),
);
};
}
/**
* Get the existing olm sessions for the given devices, and the devices that
* don't have olm sessions.
*
* @param {module:crypto/OlmDevice} olmDevice
*
* @param {module:base-apis~MatrixBaseApis} baseApis
*
* @param {object<string, module:crypto/deviceinfo[]>} devicesByUser
* map from userid to list of devices to ensure sessions for
*
* @return {Promise} resolves to an array. The first element of the array is a
* a map of user IDs to arrays of deviceInfo, representing the devices that
* don't have established olm sessions. The second element of the array is
* a map from userId to deviceId to {@link module:crypto~OlmSessionResult}
*/
export async function getExistingOlmSessions(
olmDevice, baseApis, devicesByUser,
) {
const devicesWithoutSession = {};
const sessions = {};
const promises = [];
for (const [userId, devices] of Object.entries(devicesByUser)) {
for (const deviceInfo of devices) {
const deviceId = deviceInfo.deviceId;
const key = deviceInfo.getIdentityKey();
promises.push((async () => {
const sessionId = await olmDevice.getSessionIdForDevice(
key, true,
);
if (sessionId === null) {
devicesWithoutSession[userId] = devicesWithoutSession[userId] || [];
devicesWithoutSession[userId].push(deviceInfo);
} else {
sessions[userId] = sessions[userId] || {};
sessions[userId][deviceId] = {
device: deviceInfo,
sessionId: sessionId,
};
}
})());
}
}
await Promise.all(promises);
return [devicesWithoutSession, sessions];
}
/**
* Try to make sure we have established olm sessions for the given devices.
@@ -115,32 +172,96 @@ module.exports.encryptMessageForDevice = async function(
* @param {module:base-apis~MatrixBaseApis} baseApis
*
* @param {object<string, module:crypto/deviceinfo[]>} devicesByUser
* map from userid to list of devices
* map from userid to list of devices to ensure sessions for
*
* @return {module:client.Promise} resolves once the sessions are complete, to
* @param {boolean} [force=false] If true, establish a new session even if one
* already exists.
*
* @param {Number} [otkTimeout] The timeout in milliseconds when requesting
* one-time keys for establishing new olm sessions.
*
* @param {Array} [failedServers] An array to fill with remote servers that
* failed to respond to one-time-key requests.
*
* @return {Promise} resolves once the sessions are complete, to
* an Object mapping from userId to deviceId to
* {@link module:crypto~OlmSessionResult}
*/
module.exports.ensureOlmSessionsForDevices = async function(
olmDevice, baseApis, devicesByUser,
export async function ensureOlmSessionsForDevices(
olmDevice, baseApis, devicesByUser, force, otkTimeout, failedServers,
) {
if (typeof force === "number") {
failedServers = otkTimeout;
otkTimeout = force;
force = false;
}
const devicesWithoutSession = [
// [userId, deviceId], ...
];
const result = {};
const resolveSession = {};
for (const userId in devicesByUser) {
if (!devicesByUser.hasOwnProperty(userId)) {
continue;
}
for (const [userId, devices] of Object.entries(devicesByUser)) {
result[userId] = {};
const devices = devicesByUser[userId];
for (let j = 0; j < devices.length; j++) {
const deviceInfo = devices[j];
for (const deviceInfo of devices) {
const deviceId = deviceInfo.deviceId;
const key = deviceInfo.getIdentityKey();
const sessionId = await olmDevice.getSessionIdForDevice(key);
if (sessionId === null) {
if (key === olmDevice.deviceCurve25519Key) {
// We should never be trying to start a session with ourself.
// Apart from talking to yourself being the first sign of madness,
// olm sessions can't do this because they get confused when
// they get a message and see that the 'other side' has started a
// new chain when this side has an active sender chain.
// If you see this message being logged in the wild, we should find
// the thing that is trying to send Olm messages to itself and fix it.
logger.info("Attempted to start session with ourself! Ignoring");
// We must fill in the section in the return value though, as callers
// expect it to be there.
result[userId][deviceId] = {
device: deviceInfo,
sessionId: null,
};
continue;
}
if (!olmDevice._sessionsInProgress[key]) {
// pre-emptively mark the session as in-progress to avoid race
// conditions. If we find that we already have a session, then
// we'll resolve
olmDevice._sessionsInProgress[key] = new Promise(
(resolve, reject) => {
resolveSession[key] = {
resolve: (...args) => {
delete olmDevice._sessionsInProgress[key];
resolve(...args);
},
reject: (...args) => {
delete olmDevice._sessionsInProgress[key];
reject(...args);
},
};
},
);
}
const sessionId = await olmDevice.getSessionIdForDevice(
key, resolveSession[key],
);
if (sessionId !== null && resolveSession[key]) {
// we found a session, but we had marked the session as
// in-progress, so unmark it and unblock anything that was
// waiting
delete olmDevice._sessionsInProgress[key];
resolveSession[key].resolve();
delete resolveSession[key];
}
if (sessionId === null || force) {
if (force) {
logger.info("Forcing new Olm session for " + userId + ":" + deviceId);
} else {
logger.info("Making new Olm session for " + userId + ":" + deviceId);
}
devicesWithoutSession.push([userId, deviceId]);
}
result[userId][deviceId] = {
@@ -154,29 +275,41 @@ module.exports.ensureOlmSessionsForDevices = async function(
return result;
}
// TODO: this has a race condition - if we try to send another message
// while we are claiming a key, we will end up claiming two and setting up
// two sessions.
//
// That should eventually resolve itself, but it's poor form.
const oneTimeKeyAlgorithm = "signed_curve25519";
const res = await baseApis.claimOneTimeKeys(
devicesWithoutSession, oneTimeKeyAlgorithm,
);
let res;
try {
res = await baseApis.claimOneTimeKeys(
devicesWithoutSession, oneTimeKeyAlgorithm, otkTimeout,
);
} catch (e) {
for (const resolver of Object.values(resolveSession)) {
resolver.resolve();
}
logger.log("failed to claim one-time keys", e, devicesWithoutSession);
throw e;
}
if (failedServers && "failures" in res) {
failedServers.push(...Object.keys(res.failures));
}
const otk_res = res.one_time_keys || {};
const promises = [];
for (const userId in devicesByUser) {
if (!devicesByUser.hasOwnProperty(userId)) {
continue;
}
for (const [userId, devices] of Object.entries(devicesByUser)) {
const userRes = otk_res[userId] || {};
const devices = devicesByUser[userId];
for (let j = 0; j < devices.length; j++) {
const deviceInfo = devices[j];
const deviceId = deviceInfo.deviceId;
if (result[userId][deviceId].sessionId) {
const key = deviceInfo.getIdentityKey();
if (key === olmDevice.deviceCurve25519Key) {
// We've already logged about this above. Skip here too
// otherwise we'll log saying there are no one-time keys
// which will be confusing.
continue;
}
if (result[userId][deviceId].sessionId && !force) {
// we already have a result for this device
continue;
}
@@ -190,10 +323,12 @@ module.exports.ensureOlmSessionsForDevices = async function(
}
if (!oneTimeKey) {
console.warn(
"No one-time keys (alg=" + oneTimeKeyAlgorithm +
") for device " + userId + ":" + deviceId,
);
const msg = "No one-time keys (alg=" + oneTimeKeyAlgorithm +
") for device " + userId + ":" + deviceId;
logger.warn(msg);
if (resolveSession[key]) {
resolveSession[key].resolve();
}
continue;
}
@@ -201,7 +336,15 @@ module.exports.ensureOlmSessionsForDevices = async function(
_verifyKeyAndStartSession(
olmDevice, oneTimeKey, userId, deviceInfo,
).then((sid) => {
if (resolveSession[key]) {
resolveSession[key].resolve(sid);
}
result[userId][deviceId].sessionId = sid;
}, (e) => {
if (resolveSession[key]) {
resolveSession[key].resolve();
}
throw e;
}),
);
}
@@ -209,17 +352,17 @@ module.exports.ensureOlmSessionsForDevices = async function(
await Promise.all(promises);
return result;
};
}
async function _verifyKeyAndStartSession(olmDevice, oneTimeKey, userId, deviceInfo) {
const deviceId = deviceInfo.deviceId;
try {
await _verifySignature(
await verifySignature(
olmDevice, oneTimeKey, userId, deviceId,
deviceInfo.getFingerprint(),
);
} catch (e) {
console.error(
logger.error(
"Unable to verify signature on one-time key for device " +
userId + ":" + deviceId + ":", e,
);
@@ -233,12 +376,12 @@ async function _verifyKeyAndStartSession(olmDevice, oneTimeKey, userId, deviceIn
);
} catch (e) {
// possibly a bad key
console.error("Error starting session with device " +
logger.error("Error starting olm session with device " +
userId + ":" + deviceId + ": " + e);
return null;
}
console.log("Started new sessionid " + sid +
logger.log("Started new olm sessionid " + sid +
" for device " + userId + ":" + deviceId);
return sid;
}
@@ -249,8 +392,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
*
@@ -261,7 +403,7 @@ async function _verifyKeyAndStartSession(olmDevice, oneTimeKey, userId, deviceIn
* Returns a promise which resolves (to undefined) if the the signature is good,
* or rejects with an Error if it is bad.
*/
const _verifySignature = module.exports.verifySignature = async function(
export async function verifySignature(
olmDevice, obj, signingUserId, signingDeviceId, signingKey,
) {
const signKeyId = "ed25519:" + signingDeviceId;
@@ -274,11 +416,101 @@ const _verifySignature = module.exports.verifySignature = async function(
// prepare the canonical json: remove unsigned and signatures, and stringify with
// anotherjson
delete obj.unsigned;
delete obj.signatures;
const json = anotherjson.stringify(obj);
const mangledObj = Object.assign({}, obj);
delete mangledObj.unsigned;
delete mangledObj.signatures;
const json = anotherjson.stringify(mangledObj);
olmDevice.verifySignature(
signingKey, json, signature,
);
};
}
/**
* Sign a JSON object using public key cryptography
* @param {Object} obj Object to sign. The object will be modified to include
* the new signature
* @param {Olm.PkSigning|Uint8Array} key the signing object or the private key
* seed
* @param {string} userId The user ID who owns the signing key
* @param {string} pubkey The public key (ignored if key is a seed)
* @returns {string} the signature for the object
*/
export function pkSign(obj, key, userId, pubkey) {
let createdKey = false;
if (key instanceof Uint8Array) {
const keyObj = new global.Olm.PkSigning();
pubkey = keyObj.init_with_seed(key);
key = keyObj;
createdKey = true;
}
const sigs = obj.signatures || {};
delete obj.signatures;
const unsigned = obj.unsigned;
if (obj.unsigned) delete obj.unsigned;
try {
const mysigs = sigs[userId] || {};
sigs[userId] = mysigs;
return mysigs['ed25519:' + pubkey] = key.sign(anotherjson.stringify(obj));
} finally {
obj.signatures = sigs;
if (unsigned) obj.unsigned = unsigned;
if (createdKey) {
key.free();
}
}
}
/**
* Verify a signed JSON object
* @param {Object} obj Object to verify
* @param {string} pubkey The public key to use to verify
* @param {string} userId The user ID who signed the object
*/
export function pkVerify(obj, pubkey, userId) {
const keyId = "ed25519:" + pubkey;
if (!(obj.signatures && obj.signatures[userId] && obj.signatures[userId][keyId])) {
throw new Error("No signature");
}
const signature = obj.signatures[userId][keyId];
const util = new global.Olm.Utility();
const sigs = obj.signatures;
delete obj.signatures;
const unsigned = obj.unsigned;
if (obj.unsigned) delete obj.unsigned;
try {
util.ed25519_verify(pubkey, anotherjson.stringify(obj), signature);
} finally {
obj.signatures = sigs;
if (unsigned) obj.unsigned = unsigned;
util.free();
}
}
/**
* Encode a typed array of uint8 as base64.
* @param {Uint8Array} uint8Array The data to encode.
* @return {string} The base64.
*/
export function encodeBase64(uint8Array) {
return Buffer.from(uint8Array).toString("base64");
}
/**
* Encode a typed array of uint8 as unpadded base64.
* @param {Uint8Array} uint8Array The data to encode.
* @return {string} The unpadded base64.
*/
export function encodeUnpaddedBase64(uint8Array) {
return encodeBase64(uint8Array).replace(/=+$/g, '');
}
/**
* Decode a base64 string to a typed array of uint8.
* @param {string} base64 The base64 to decode.
* @return {Uint8Array} The decoded data.
*/
export function decodeBase64(base64) {
return Buffer.from(base64, "base64");
}
+66
View File
@@ -0,0 +1,66 @@
/*
Copyright 2018 New Vector Ltd
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 bs58 from 'bs58';
// picked arbitrarily but to try & avoid clashing with any bitcoin ones
// (which are also base58 encoded, but bitcoin's involve a lot more hashing)
const OLM_RECOVERY_KEY_PREFIX = [0x8B, 0x01];
export function encodeRecoveryKey(key) {
const buf = new Buffer(OLM_RECOVERY_KEY_PREFIX.length + key.length + 1);
buf.set(OLM_RECOVERY_KEY_PREFIX, 0);
buf.set(key, OLM_RECOVERY_KEY_PREFIX.length);
let parity = 0;
for (let i = 0; i < buf.length - 1; ++i) {
parity ^= buf[i];
}
buf[buf.length - 1] = parity;
const base58key = bs58.encode(buf);
return base58key.match(/.{1,4}/g).join(" ");
}
export function decodeRecoveryKey(recoverykey) {
const result = bs58.decode(recoverykey.replace(/ /g, ''));
let parity = 0;
for (const b of result) {
parity ^= b;
}
if (parity !== 0) {
throw new Error("Incorrect parity");
}
for (let i = 0; i < OLM_RECOVERY_KEY_PREFIX.length; ++i) {
if (result[i] !== OLM_RECOVERY_KEY_PREFIX[i]) {
throw new Error("Incorrect prefix");
}
}
if (
result.length !==
OLM_RECOVERY_KEY_PREFIX.length + global.Olm.PRIVATE_KEY_LENGTH + 1
) {
throw new Error("Incorrect length");
}
return Uint8Array.from(result.slice(
OLM_RECOVERY_KEY_PREFIX.length,
OLM_RECOVERY_KEY_PREFIX.length + global.Olm.PRIVATE_KEY_LENGTH,
));
}
@@ -1,7 +1,25 @@
import Promise from 'bluebird';
import utils from '../../utils';
/*
Copyright 2017 Vector Creations Ltd
Copyright 2018 New Vector Ltd
Copyright 2020 The Matrix.org Foundation C.I.C.
export const VERSION = 1;
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 * as utils from "../../utils";
export const VERSION = 9;
/**
* Implementation of a CryptoStore which is backed by an existing
@@ -21,7 +39,7 @@ export class Backend {
// attempts to delete the database will block (and subsequent
// attempts to re-create it will also block).
db.onversionchange = (ev) => {
console.log(`versionchange for indexeddb ${this._dbName}: closing`);
logger.log(`versionchange for indexeddb ${this._dbName}: closing`);
db.close();
};
}
@@ -39,35 +57,34 @@ export class Backend {
getOrAddOutgoingRoomKeyRequest(request) {
const requestBody = request.requestBody;
const deferred = Promise.defer();
const txn = this._db.transaction("outgoingRoomKeyRequests", "readwrite");
txn.onerror = deferred.reject;
return new Promise((resolve, reject) => {
const txn = this._db.transaction("outgoingRoomKeyRequests", "readwrite");
txn.onerror = reject;
// first see if we already have an entry for this request.
this._getOutgoingRoomKeyRequest(txn, requestBody, (existing) => {
if (existing) {
// this entry matches the request - return it.
console.log(
`already have key request outstanding for ` +
`${requestBody.room_id} / ${requestBody.session_id}: ` +
`not sending another`,
// first see if we already have an entry for this request.
this._getOutgoingRoomKeyRequest(txn, requestBody, (existing) => {
if (existing) {
// this entry matches the request - return it.
logger.log(
`already have key request outstanding for ` +
`${requestBody.room_id} / ${requestBody.session_id}: ` +
`not sending another`,
);
resolve(existing);
return;
}
// we got to the end of the list without finding a match
// - add the new request.
logger.log(
`enqueueing key request for ${requestBody.room_id} / ` +
requestBody.session_id,
);
deferred.resolve(existing);
return;
}
// we got to the end of the list without finding a match
// - add the new request.
console.log(
`enqueueing key request for ${requestBody.room_id} / ` +
requestBody.session_id,
);
const store = txn.objectStore("outgoingRoomKeyRequests");
store.add(request);
txn.onsuccess = () => { deferred.resolve(request); };
txn.oncomplete = () => {resolve(request);};
const store = txn.objectStore("outgoingRoomKeyRequests");
store.add(request);
});
});
return deferred.promise;
}
/**
@@ -81,15 +98,14 @@ export class Backend {
* not found
*/
getOutgoingRoomKeyRequest(requestBody) {
const deferred = Promise.defer();
return new Promise((resolve, reject) => {
const txn = this._db.transaction("outgoingRoomKeyRequests", "readonly");
txn.onerror = reject;
const txn = this._db.transaction("outgoingRoomKeyRequests", "readonly");
txn.onerror = deferred.reject;
this._getOutgoingRoomKeyRequest(txn, requestBody, (existing) => {
deferred.resolve(existing);
this._getOutgoingRoomKeyRequest(txn, requestBody, (existing) => {
resolve(existing);
});
});
return deferred.promise;
}
/**
@@ -187,6 +203,59 @@ export class Backend {
return promiseifyTxn(txn).then(() => result);
}
/**
*
* @param {Number} wantedState
* @return {Promise<Array<*>>} All elements in a given state
*/
getAllOutgoingRoomKeyRequestsByState(wantedState) {
return new Promise((resolve, reject) => {
const txn = this._db.transaction("outgoingRoomKeyRequests", "readonly");
const store = txn.objectStore("outgoingRoomKeyRequests");
const index = store.index("state");
const request = index.getAll(wantedState);
request.onsuccess = (ev) => resolve(ev.target.result);
request.onerror = (ev) => reject(ev.target.error);
});
}
getOutgoingRoomKeyRequestsByTarget(userId, deviceId, wantedStates) {
let stateIndex = 0;
const results = [];
function onsuccess(ev) {
const cursor = ev.target.result;
if (cursor) {
const keyReq = cursor.value;
if (keyReq.recipients.includes({userId, deviceId})) {
results.push(keyReq);
}
cursor.continue();
} else {
// try the next state in the list
stateIndex++;
if (stateIndex >= wantedStates.length) {
// no matches
return;
}
const wantedState = wantedStates[stateIndex];
const cursorReq = ev.target.source.openCursor(wantedState);
cursorReq.onsuccess = onsuccess;
}
}
const txn = this._db.transaction("outgoingRoomKeyRequests", "readonly");
const store = txn.objectStore("outgoingRoomKeyRequests");
const wantedState = wantedStates[stateIndex];
const cursorReq = store.index("state").openCursor(wantedState);
cursorReq.onsuccess = onsuccess;
return promiseifyTxn(txn).then(() => results);
}
/**
* Look for an existing room key request by id and state, and update it if
* found
@@ -209,7 +278,7 @@ export class Backend {
}
const data = cursor.value;
if (data.state != expectedState) {
console.warn(
logger.warn(
`Cannot update room key request from ${expectedState} ` +
`as it was already updated to ${data.state}`,
);
@@ -247,7 +316,7 @@ export class Backend {
}
const data = cursor.value;
if (data.state != expectedState) {
console.warn(
logger.warn(
`Cannot delete room key request in state ${data.state} `
+ `(expected ${expectedState})`,
);
@@ -257,16 +326,495 @@ export class Backend {
};
return promiseifyTxn(txn);
}
// Olm Account
getAccount(txn, func) {
const objectStore = txn.objectStore("account");
const getReq = objectStore.get("-");
getReq.onsuccess = function() {
try {
func(getReq.result || null);
} catch (e) {
abortWithException(txn, e);
}
};
}
storeAccount(txn, newData) {
const objectStore = txn.objectStore("account");
objectStore.put(newData, "-");
}
getCrossSigningKeys(txn, func) {
const objectStore = txn.objectStore("account");
const getReq = objectStore.get("crossSigningKeys");
getReq.onsuccess = function() {
try {
func(getReq.result || null);
} catch (e) {
abortWithException(txn, e);
}
};
}
getSecretStorePrivateKey(txn, func, type) {
const objectStore = txn.objectStore("account");
const getReq = objectStore.get(`ssss_cache:${type}`);
getReq.onsuccess = function() {
try {
func(getReq.result || null);
} catch (e) {
abortWithException(txn, e);
}
};
}
storeCrossSigningKeys(txn, keys) {
const objectStore = txn.objectStore("account");
objectStore.put(keys, "crossSigningKeys");
}
storeSecretStorePrivateKey(txn, type, key) {
const objectStore = txn.objectStore("account");
objectStore.put(key, `ssss_cache:${type}`);
}
// Olm Sessions
countEndToEndSessions(txn, func) {
const objectStore = txn.objectStore("sessions");
const countReq = objectStore.count();
countReq.onsuccess = function() {
try {
func(countReq.result);
} catch (e) {
abortWithException(txn, e);
}
};
}
getEndToEndSessions(deviceKey, txn, func) {
const objectStore = txn.objectStore("sessions");
const idx = objectStore.index("deviceKey");
const getReq = idx.openCursor(deviceKey);
const results = {};
getReq.onsuccess = function() {
const cursor = getReq.result;
if (cursor) {
results[cursor.value.sessionId] = {
session: cursor.value.session,
lastReceivedMessageTs: cursor.value.lastReceivedMessageTs,
};
cursor.continue();
} else {
try {
func(results);
} catch (e) {
abortWithException(txn, e);
}
}
};
}
getEndToEndSession(deviceKey, sessionId, txn, func) {
const objectStore = txn.objectStore("sessions");
const getReq = objectStore.get([deviceKey, sessionId]);
getReq.onsuccess = function() {
try {
if (getReq.result) {
func({
session: getReq.result.session,
lastReceivedMessageTs: getReq.result.lastReceivedMessageTs,
});
} else {
func(null);
}
} catch (e) {
abortWithException(txn, e);
}
};
}
getAllEndToEndSessions(txn, func) {
const objectStore = txn.objectStore("sessions");
const getReq = objectStore.openCursor();
getReq.onsuccess = function() {
try {
const cursor = getReq.result;
if (cursor) {
func(cursor.value);
cursor.continue();
} else {
func(null);
}
} catch (e) {
abortWithException(txn, e);
}
};
}
storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn) {
const objectStore = txn.objectStore("sessions");
objectStore.put({
deviceKey,
sessionId,
session: sessionInfo.session,
lastReceivedMessageTs: sessionInfo.lastReceivedMessageTs,
});
}
async storeEndToEndSessionProblem(deviceKey, type, fixed) {
const txn = this._db.transaction("session_problems", "readwrite");
const objectStore = txn.objectStore("session_problems");
objectStore.put({
deviceKey,
type,
fixed,
time: Date.now(),
});
return promiseifyTxn(txn);
}
async getEndToEndSessionProblem(deviceKey, timestamp) {
let result;
const txn = this._db.transaction("session_problems", "readwrite");
const objectStore = txn.objectStore("session_problems");
const index = objectStore.index("deviceKey");
const req = index.getAll(deviceKey);
req.onsuccess = (event) => {
const problems = req.result;
if (!problems.length) {
result = null;
return;
}
problems.sort((a, b) => {
return a.time - b.time;
});
const lastProblem = problems[problems.length - 1];
for (const problem of problems) {
if (problem.time > timestamp) {
result = Object.assign({}, problem, {fixed: lastProblem.fixed});
return;
}
}
if (lastProblem.fixed) {
result = null;
} else {
result = lastProblem;
}
};
await promiseifyTxn(txn);
return result;
}
// FIXME: we should probably prune this when devices get deleted
async filterOutNotifiedErrorDevices(devices) {
const txn = this._db.transaction("notified_error_devices", "readwrite");
const objectStore = txn.objectStore("notified_error_devices");
const ret = [];
await Promise.all(devices.map((device) => {
return new Promise((resolve) => {
const {userId, deviceInfo} = device;
const getReq = objectStore.get([userId, deviceInfo.deviceId]);
getReq.onsuccess = function() {
if (!getReq.result) {
objectStore.put({userId, deviceId: deviceInfo.deviceId});
ret.push(device);
}
resolve();
};
});
}));
return ret;
}
// Inbound group sessions
getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) {
let session = false;
let withheld = false;
const objectStore = txn.objectStore("inbound_group_sessions");
const getReq = objectStore.get([senderCurve25519Key, sessionId]);
getReq.onsuccess = function() {
try {
if (getReq.result) {
session = getReq.result.session;
} else {
session = null;
}
if (withheld !== false) {
func(session, withheld);
}
} catch (e) {
abortWithException(txn, e);
}
};
const withheldObjectStore = txn.objectStore("inbound_group_sessions_withheld");
const withheldGetReq = withheldObjectStore.get([senderCurve25519Key, sessionId]);
withheldGetReq.onsuccess = function() {
try {
if (withheldGetReq.result) {
withheld = withheldGetReq.result.session;
} else {
withheld = null;
}
if (session !== false) {
func(session, withheld);
}
} catch (e) {
abortWithException(txn, e);
}
};
}
getAllEndToEndInboundGroupSessions(txn, func) {
const objectStore = txn.objectStore("inbound_group_sessions");
const getReq = objectStore.openCursor();
getReq.onsuccess = function() {
const cursor = getReq.result;
if (cursor) {
try {
func({
senderKey: cursor.value.senderCurve25519Key,
sessionId: cursor.value.sessionId,
sessionData: cursor.value.session,
});
} catch (e) {
abortWithException(txn, e);
}
cursor.continue();
} else {
try {
func(null);
} catch (e) {
abortWithException(txn, e);
}
}
};
}
addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) {
const objectStore = txn.objectStore("inbound_group_sessions");
const addReq = objectStore.add({
senderCurve25519Key, sessionId, session: sessionData,
});
addReq.onerror = (ev) => {
if (addReq.error.name === 'ConstraintError') {
// This stops the error from triggering the txn's onerror
ev.stopPropagation();
// ...and this stops it from aborting the transaction
ev.preventDefault();
logger.log(
"Ignoring duplicate inbound group session: " +
senderCurve25519Key + " / " + sessionId,
);
} else {
abortWithException(txn, new Error(
"Failed to add inbound group session: " + addReq.error,
));
}
};
}
storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) {
const objectStore = txn.objectStore("inbound_group_sessions");
objectStore.put({
senderCurve25519Key, sessionId, session: sessionData,
});
}
storeEndToEndInboundGroupSessionWithheld(
senderCurve25519Key, sessionId, sessionData, txn,
) {
const objectStore = txn.objectStore("inbound_group_sessions_withheld");
objectStore.put({
senderCurve25519Key, sessionId, session: sessionData,
});
}
getEndToEndDeviceData(txn, func) {
const objectStore = txn.objectStore("device_data");
const getReq = objectStore.get("-");
getReq.onsuccess = function() {
try {
func(getReq.result || null);
} catch (e) {
abortWithException(txn, e);
}
};
}
storeEndToEndDeviceData(deviceData, txn) {
const objectStore = txn.objectStore("device_data");
objectStore.put(deviceData, "-");
}
storeEndToEndRoom(roomId, roomInfo, txn) {
const objectStore = txn.objectStore("rooms");
objectStore.put(roomInfo, roomId);
}
getEndToEndRooms(txn, func) {
const rooms = {};
const objectStore = txn.objectStore("rooms");
const getReq = objectStore.openCursor();
getReq.onsuccess = function() {
const cursor = getReq.result;
if (cursor) {
rooms[cursor.key] = cursor.value;
cursor.continue();
} else {
try {
func(rooms);
} catch (e) {
abortWithException(txn, e);
}
}
};
}
// session backups
getSessionsNeedingBackup(limit) {
return new Promise((resolve, reject) => {
const sessions = [];
const txn = this._db.transaction(
["sessions_needing_backup", "inbound_group_sessions"],
"readonly",
);
txn.onerror = reject;
txn.oncomplete = function() {
resolve(sessions);
};
const objectStore = txn.objectStore("sessions_needing_backup");
const sessionStore = txn.objectStore("inbound_group_sessions");
const getReq = objectStore.openCursor();
getReq.onsuccess = function() {
const cursor = getReq.result;
if (cursor) {
const sessionGetReq = sessionStore.get(cursor.key);
sessionGetReq.onsuccess = function() {
sessions.push({
senderKey: sessionGetReq.result.senderCurve25519Key,
sessionId: sessionGetReq.result.sessionId,
sessionData: sessionGetReq.result.session,
});
};
if (!limit || sessions.length < limit) {
cursor.continue();
}
}
};
});
}
countSessionsNeedingBackup(txn) {
if (!txn) {
txn = this._db.transaction("sessions_needing_backup", "readonly");
}
const objectStore = txn.objectStore("sessions_needing_backup");
return new Promise((resolve, reject) => {
const req = objectStore.count();
req.onerror = reject;
req.onsuccess = () => resolve(req.result);
});
}
unmarkSessionsNeedingBackup(sessions, txn) {
if (!txn) {
txn = this._db.transaction("sessions_needing_backup", "readwrite");
}
const objectStore = txn.objectStore("sessions_needing_backup");
return Promise.all(sessions.map((session) => {
return new Promise((resolve, reject) => {
const req = objectStore.delete([session.senderKey, session.sessionId]);
req.onsuccess = resolve;
req.onerror = reject;
});
}));
}
markSessionsNeedingBackup(sessions, txn) {
if (!txn) {
txn = this._db.transaction("sessions_needing_backup", "readwrite");
}
const objectStore = txn.objectStore("sessions_needing_backup");
return Promise.all(sessions.map((session) => {
return new Promise((resolve, reject) => {
const req = objectStore.put({
senderCurve25519Key: session.senderKey,
sessionId: session.sessionId,
});
req.onsuccess = resolve;
req.onerror = reject;
});
}));
}
doTxn(mode, stores, func) {
const txn = this._db.transaction(stores, mode);
const promise = promiseifyTxn(txn);
const result = func(txn);
return promise.then(() => {
return result;
});
}
}
export function upgradeDatabase(db, oldVersion) {
console.log(
logger.log(
`Upgrading IndexedDBCryptoStore from version ${oldVersion}`
+ ` to ${VERSION}`,
);
if (oldVersion < 1) { // The database did not previously exist.
createDatabase(db);
}
if (oldVersion < 2) {
db.createObjectStore("account");
}
if (oldVersion < 3) {
const sessionsStore = db.createObjectStore("sessions", {
keyPath: ["deviceKey", "sessionId"],
});
sessionsStore.createIndex("deviceKey", "deviceKey");
}
if (oldVersion < 4) {
db.createObjectStore("inbound_group_sessions", {
keyPath: ["senderCurve25519Key", "sessionId"],
});
}
if (oldVersion < 5) {
db.createObjectStore("device_data");
}
if (oldVersion < 6) {
db.createObjectStore("rooms");
}
if (oldVersion < 7) {
db.createObjectStore("sessions_needing_backup", {
keyPath: ["senderCurve25519Key", "sessionId"],
});
}
if (oldVersion < 8) {
db.createObjectStore("inbound_group_sessions_withheld", {
keyPath: ["senderCurve25519Key", "sessionId"],
});
}
if (oldVersion < 9) {
const problemsStore = db.createObjectStore("session_problems", {
keyPath: ["deviceKey", "time"],
});
problemsStore.createIndex("deviceKey", "deviceKey");
db.createObjectStore("notified_error_devices", {
keyPath: ["userId", "deviceId"],
});
}
// Expand as needed.
}
@@ -283,9 +831,46 @@ function createDatabase(db) {
outgoingRoomKeyRequestsStore.createIndex("state", "state");
}
/*
* Aborts a transaction with a given exception
* The transaction promise will be rejected with this exception.
*/
function abortWithException(txn, e) {
// We cheekily stick our exception onto the transaction object here
// We could alternatively make the thing we pass back to the app
// an object containing the transaction and exception.
txn._mx_abortexception = e;
try {
txn.abort();
} catch (e) {
// sometimes we won't be able to abort the transaction
// (ie. if it's aborted or completed)
}
}
function promiseifyTxn(txn) {
return new Promise((resolve, reject) => {
txn.oncomplete = resolve;
txn.onerror = reject;
txn.oncomplete = () => {
if (txn._mx_abortexception !== undefined) {
reject(txn._mx_abortexception);
}
resolve();
};
txn.onerror = (event) => {
if (txn._mx_abortexception !== undefined) {
reject(txn._mx_abortexception);
} else {
logger.log("Error performing indexeddb txn", event);
reject(event.target.error);
}
};
txn.onabort = (event) => {
if (txn._mx_abortexception !== undefined) {
reject(txn._mx_abortexception);
} else {
logger.log("Error performing indexeddb txn", event);
reject(event.target.error);
}
};
});
}

Some files were not shown because too many files have changed in this diff Show More