Compare commits

..

34 Commits

Author SHA1 Message Date
Matthew Hodgson 9a81ca9fab v0.7.10 2017-06-02 01:02:01 +01:00
Matthew Hodgson 0edf19a871 Prepare changelog for v0.7.10 2017-06-02 01:02:01 +01:00
Matthew Hodgson 6989f6c835 switch to using new media constraints to allow device selection to work 2017-06-01 21:57:58 +01:00
Richard van der Hoff de844f1a32 Merge pull request #447 from matrix-org/rav/fix_indexeddb_deletion
indexeddb-crypto-store: fix db deletion
2017-06-01 17:34:37 +01:00
Richard van der Hoff 97951e1c1a Merge pull request #446 from matrix-org/rav/load_olm_from_global
Load Olm from the global rather than requiring it.
2017-06-01 15:41:22 +01:00
Richard van der Hoff 2edbed8528 indexeddb-crypto-store: fix db deletion
Add an `onversionchange` listener to close the db, so that we can delete it
without blocking.
2017-06-01 15:37:27 +01:00
Richard van der Hoff 24937910c7 Merge remote-tracking branch 'origin/develop' into rav/load_olm_from_global 2017-06-01 15:31:27 +01:00
Richard van der Hoff 5cd441fb48 Add a warning to the changelog 2017-06-01 15:30:00 +01:00
Richard van der Hoff 06b956bd75 disable e2e test when there is no e2e 2017-06-01 13:16:10 +01:00
Richard van der Hoff 41864d46c3 Load Olm from the global rather than requiring it.
This means that we can avoid confusing everybody in the world about how to
webpack js-sdk apps.
2017-06-01 13:09:48 +01:00
Matthew Hodgson f6622e0bcd unbreak riot-web release process 2017-06-01 02:41:47 +01:00
Matthew Hodgson 0f30d21fa2 v0.7.9 2017-06-01 01:41:06 +01:00
Matthew Hodgson 4257c8c9f5 Prepare changelog for v0.7.9 2017-06-01 01:41:06 +01:00
Richard van der Hoff 331859d383 Merge pull request #445 from matrix-org/rav/indexeddb_crypto_store
Initial framework for indexeddb-backed crypto store
2017-05-31 18:06:45 +01:00
Richard van der Hoff ef03b708a8 Add MatrixClient.clearStores
- to clear both sets of storage on logout
2017-05-31 17:22:07 +01:00
Richard van der Hoff 716d098361 Address Kegan's review comments
jsdoc mostly.
2017-05-31 16:05:00 +01:00
Richard van der Hoff d887057660 Merge pull request #444 from matrix-org/rav/factor_out_reemit
Factor out reEmit to a common module
2017-05-31 14:22:34 +01:00
Richard van der Hoff 7efbfebb4d Factor out reEmit to a common module
and rewrite it to use modern JS while we're at it
2017-05-31 11:01:48 +01:00
Richard van der Hoff 4c7afe5af0 Initial framework for indexeddb-backed crypto store
Doesn't do anything useful yet - just demonstrates a framework for how I hope
it will fit into the sdk.
2017-05-30 23:25:07 +01:00
Richard van der Hoff 676515cf27 Merge pull request #443 from matrix-org/rav/es6ify_algorithm_base
crypto/algorithms/base.js: Convert to es6
2017-05-23 16:36:42 +01:00
Richard van der Hoff 0eb5b0fdfa Merge pull request #435 from t3chguy/maySendRedactionForEvent
maySendRedactionForEvent for userId
2017-05-23 15:45:46 +01:00
Richard van der Hoff 2feba4787f Merge pull request #441 from matrix-org/rav/get_userid
MatrixClient: add getUserId()
2017-05-23 15:44:34 +01:00
Michael Telatynski 516dc1043e prevent powerLevels being undef
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2017-05-23 14:53:11 +01:00
Richard van der Hoff b26c1c57dc crypto/algorithms/base.js: Convert to es6
Convert base to an es6 module with es6 classes, for clarity and to help with
jsdoccing.

Complications are:

* jsdoc gets confused by `export class`, so the exports are separated.

* turns out that extending Error is a bit difficult, so instanceof doesn't work
  on derived Error classes. This only really affects us in one place (app-side
  code shouldn't be doing instanceofs anyway), so just use `name` instead.
2017-05-23 14:32:13 +01:00
Richard van der Hoff 0945ba9e90 Merge pull request #442 from matrix-org/rav/custom_babel_for_jsdoc
Run jsdoc on a custom babeling of the source
2017-05-23 14:28:41 +01:00
Michael Telatynski 69ed6f283d fix based on rich's feedback
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2017-05-23 12:55:05 +01:00
Richard van der Hoff 9eef850d0c Run jsdoc on a custom babeling of the source
jsdoc can't read our raw source, because of our dangling commas in function
calls. On the other hand, running on /lib means that a lot of the useful
information about exports is lost and you end up having to jump through hoops
to get jsdoc to generate the right thing.

This uses a separate run of babel (with all the presets turned off) to generate
source which is almost identical to the input, but lacks trailing commas.

(https://babeljs.io/blog/2015/10/31/setting-up-babel-6 says 'Babel 6 ships
without any default transforms, so when you run Babel on a file it will just
print it back out to you without changing anything.' - however, that is,
empirically, not entirely true.)
2017-05-23 12:26:17 +01:00
Richard van der Hoff cf1574d690 MatrixClient: add getUserId()
... I'm amazed we got this far without it.
2017-05-23 10:37:26 +01:00
David Baker d6913e41a0 Merge branch 'master' into develop 2017-05-22 11:33:20 +01:00
Richard van der Hoff 43989be768 Merge pull request #439 from kscz/add_getstoreddeviceforuser
Add in a public api getStoredDevice allowing clients to get a specific device
2017-05-22 09:36:38 +01:00
Kit Sczudlo 822380ac38 Add in a public api getStoredDevice allowing clients to get a specific device
Signed-off-by: Kit Sczudlo <kit@kitscz.com>
2017-05-21 00:30:40 -07:00
Michael Telatynski 09529a1aa8 lets please the ESLint gods
`--max-warnings 115` :')

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2017-05-16 14:16:42 +01:00
Michael Telatynski d182fd6bb7 can't redact queued/not_sent
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2017-05-16 14:12:29 +01:00
Michael Telatynski 36bf123e2b maySendRedactionForEvent for userId
done using a private helper so kick/ban etc perms can be done
easily at a later stage

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2017-05-16 14:04:55 +01:00
20 changed files with 562 additions and 185 deletions
+3 -1
View File
@@ -1,4 +1,6 @@
.jsdoc
/.jsdocbuild
/.jsdoc
node_modules
.lock-wscript
build/Release
+34
View File
@@ -1,3 +1,37 @@
Changes in [0.7.10](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.7.10) (2017-06-02)
==================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.7.9...v0.7.10)
* BREAKING CHANGE: The SDK no longer ``require``s ``olm`` - instead it expects
libolm to be provided as an ``Olm`` global. This will only affect
applications which use end-to-end encryption. See the
[README](README.md#end-to-end-encryption-support) for details.
* indexeddb-crypto-store: fix db deletion
[\#447](https://github.com/matrix-org/matrix-js-sdk/pull/447)
* Load Olm from the global rather than requiring it.
[\#446](https://github.com/matrix-org/matrix-js-sdk/pull/446)
Changes in [0.7.9](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.7.9) (2017-06-01)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.7.8...v0.7.9)
* Initial framework for indexeddb-backed crypto store
[\#445](https://github.com/matrix-org/matrix-js-sdk/pull/445)
* Factor out reEmit to a common module
[\#444](https://github.com/matrix-org/matrix-js-sdk/pull/444)
* crypto/algorithms/base.js: Convert to es6
[\#443](https://github.com/matrix-org/matrix-js-sdk/pull/443)
* maySendRedactionForEvent for userId
[\#435](https://github.com/matrix-org/matrix-js-sdk/pull/435)
* MatrixClient: add getUserId()
[\#441](https://github.com/matrix-org/matrix-js-sdk/pull/441)
* Run jsdoc on a custom babeling of the source
[\#442](https://github.com/matrix-org/matrix-js-sdk/pull/442)
* Add in a public api getStoredDevice allowing clients to get a specific
device
[\#439](https://github.com/matrix-org/matrix-js-sdk/pull/439)
Changes in [0.7.8](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.7.8) (2017-05-22)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.7.8-rc.1...v0.7.8)
+18
View File
@@ -236,6 +236,24 @@ host the API reference from the source files like this:
Then visit ``http://localhost:8005`` to see the API docs.
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.
To enable support in a browser application:
* download the transpiled libolm (either via ``npm install olm``, or from
https://matrix.org/packages/npm/olm/).
* load ``olm.js`` as a ``<script>`` *before* ``browser-matrix.js``.
To enable support in a node.js application:
* ``npm install olm``
* ``require('olm');`` *before* ``matrix-js-sdk``.
Contributing
============
*This section is for people who want to modify the SDK. If you just
+9
View File
@@ -1,4 +1,13 @@
var matrixcs = require("./lib/matrix");
matrixcs.request(require("browser-request"));
matrixcs.setCryptoStoreFactory(
function() {
return new matrixcs.IndexedDBCryptoStore(
global.indexedDB, "matrix-js-sdk:crypto"
);
}
);
module.exports = matrixcs; // keep export for browserify package deps
global.matrixcs = matrixcs;
+4 -11
View File
@@ -1,6 +1,6 @@
{
"name": "matrix-js-sdk",
"version": "0.7.8",
"version": "0.7.10",
"description": "Matrix Client-Server SDK for Javascript",
"main": "index.js",
"scripts": {
@@ -8,12 +8,12 @@
"test:run": "istanbul cover --report text --report cobertura --config .istanbul.yml -i \"lib/**/*.js\" _mocha -- --recursive specbuild --colors --reporter mocha-jenkins-reporter --reporter-options junit_report_path=reports/test-results.xml",
"test": "npm run test:build && npm run test:run",
"check": "npm run test:build && _mocha --recursive specbuild --colors",
"gendoc": "jsdoc -r lib -P package.json -R README.md -d .jsdoc",
"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",
"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 115 src spec",
"lint": "eslint --max-warnings 113 src spec",
"prepublish": "npm run build && git rev-parse HEAD > git-revision.txt"
},
"repository": {
@@ -73,16 +73,9 @@
"uglify-js": "^2.8.26",
"watchify": "^3.2.1"
},
"optionalDependencies": {
"olm": "https://matrix.org/packages/npm/olm/olm-2.2.1.tgz"
},
"browserify": {
"transform": [
"sourceify",
"browserify-shim"
"sourceify"
]
},
"browserify-shim": {
"olm": "global:Olm"
}
}
+3 -1
View File
@@ -206,7 +206,9 @@ if [ -n "$signing_id" ]; then
tarfile="$tag.tar.gz"
gh_project_url=$(git remote get-url origin |
sed -e 's#^git@github.com:#https://github.com/#' -e 's/\.git$//')
sed -e 's#^git@github\.com:#https://github.com/#' \
-e 's#^git\+ssh://git@github\.com/#https://github.com/#' \
-e 's/\.git$//')
project_name="${gh_project_url##*/}"
curl -L "${gh_project_url}/archive/${tarfile}" -o "${tarfile}"
+4
View File
@@ -299,6 +299,10 @@ describe("MatrixClient", function() {
describe("downloadKeys", function() {
if (!sdk.CRYPTO_ENABLED) {
return;
}
it("should do an HTTP request and then store the keys", function(done) {
const ed25519key = "7wG2lzAqbjcyEkOP7O4gU7ItYcn+chKzh5sT/5r2l78";
// ed25519key = client.getDeviceEd25519Key();
+54 -3
View File
@@ -47,8 +47,7 @@ try {
var Crypto = require("./crypto");
CRYPTO_ENABLED = true;
} catch (e) {
console.error("olm load error", e);
// Olm not installed.
console.warn("Unable to load crypto module: crypto will be disabled: " + e);
}
/**
@@ -104,6 +103,9 @@ try {
* disabled by default for compatibility with older clients - in particular to
* maintain support for back-paginating the live timeline after a '/sync'
* result with a gap.
*
* @param {module:crypto.store.base~CryptoStore} opts.cryptoStore
* crypto store implementation.
*/
function MatrixClient(opts) {
MatrixBaseApis.call(this, opts);
@@ -153,13 +155,16 @@ function MatrixClient(opts) {
this._notifTimelineSet = null;
this._crypto = null;
this._cryptoStore = opts.cryptoStore;
if (CRYPTO_ENABLED && Boolean(opts.sessionStore) &&
Boolean(this._cryptoStore) &&
userId !== null && this.deviceId !== null) {
this._crypto = new Crypto(
this, this,
opts.sessionStore,
userId, this.deviceId,
this.store,
opts.cryptoStore,
);
this.olmVersion = Crypto.getOlmVersion();
@@ -168,6 +173,37 @@ function MatrixClient(opts) {
utils.inherits(MatrixClient, EventEmitter);
utils.extend(MatrixClient.prototype, MatrixBaseApis.prototype);
/**
* Clear any data out of the persistent stores used by the client.
*
* @returns {Promise} Promise which resolves when the stores have been cleared.
*/
MatrixClient.prototype.clearStores = function() {
if (this._clientRunning) {
throw new Error("Cannot clear stores while client is running");
}
const promises = [];
promises.push(this.store.deleteAllData());
if (this._cryptoStore) {
promises.push(this._cryptoStore.deleteAllData());
}
return q.all(promises);
};
/**
* Get the user-id of the logged-in user
*
* @return {?string} MXID for the logged-in user, or null if not logged in
*/
MatrixClient.prototype.getUserId = function() {
if (this.credentials && this.credentials.userId) {
return this.credentials.userId;
}
return null;
};
/**
* Get the domain for this client's MXID
* @return {?string} Domain of this MXID
@@ -359,6 +395,21 @@ MatrixClient.prototype.getStoredDevicesForUser = function(userId) {
return this._crypto.getStoredDevicesForUser(userId) || [];
};
/**
* Get the stored device key for a user id and device id
*
* @param {string} userId the user to list keys for.
* @param {string} deviceId unique identifier for the device
*
* @return {?module:crypto-deviceinfo} device or null
*/
MatrixClient.prototype.getStoredDevice = function(userId, deviceId) {
if (this._crypto === null) {
throw new Error("End-to-end encryption disabled");
}
return this._crypto.getStoredDevice(userId, deviceId) || null;
};
/**
* Mark the given device as verified
*
@@ -552,7 +603,7 @@ function _decryptEvent(client, event) {
console.warn(
`Error decrypting event (id=${event.getId()}): ${e}`,
);
if (!(e instanceof Crypto.DecryptionError)) {
if (e.name !== "DecryptionError") {
throw e;
}
_badEncryptedMessage(event, e.message);
+2 -4
View File
@@ -20,11 +20,9 @@ limitations under the License.
*
* @module crypto/OlmDevice
*/
const Olm = require("olm");
const Olm = global.Olm;
if (!Olm) {
// this happens if we were loaded via browserify and the Olm module was not
// loaded.
throw new Error("Olm is not defined");
throw new Error("global.Olm is not defined");
}
const utils = require("../utils");
+112 -114
View File
@@ -13,14 +13,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";
/**
* Internal module. Defines the base classes of the encryption implementations
*
* @module crypto/algorithms/base
* @module
*/
const utils = require("../../utils");
/**
* map of registered encryption algorithm classes. A map from string to {@link
@@ -28,7 +26,7 @@ const utils = require("../../utils");
*
* @type {Object.<string, function(new: module:crypto/algorithms/base.EncryptionAlgorithm)>}
*/
module.exports.ENCRYPTION_CLASSES = {};
export const ENCRYPTION_CLASSES = {};
/**
* map of registered encryption algorithm classes. Map from string to {@link
@@ -36,12 +34,11 @@ module.exports.ENCRYPTION_CLASSES = {};
*
* @type {Object.<string, function(new: module:crypto/algorithms/base.DecryptionAlgorithm)>}
*/
module.exports.DECRYPTION_CLASSES = {};
export const DECRYPTION_CLASSES = {};
/**
* base type for encryption implementations
*
* @constructor
* @alias module:crypto/algorithms/base.EncryptionAlgorithm
*
* @param {object} params parameters
@@ -53,45 +50,45 @@ module.exports.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
*/
const EncryptionAlgorithm = function(params) {
this._userId = params.userId;
this._deviceId = params.deviceId;
this._crypto = params.crypto;
this._olmDevice = params.olmDevice;
this._baseApis = params.baseApis;
this._roomId = params.roomId;
};
/** */
module.exports.EncryptionAlgorithm = EncryptionAlgorithm;
class EncryptionAlgorithm {
constructor(params) {
this._userId = params.userId;
this._deviceId = params.deviceId;
this._crypto = params.crypto;
this._olmDevice = params.olmDevice;
this._baseApis = params.baseApis;
this._roomId = params.roomId;
}
/**
* Encrypt a message event
*
* @method module:crypto/algorithms/base.EncryptionAlgorithm#encryptMessage
* @abstract
*
* @param {module:models/room} room
* @param {string} eventType
* @param {object} plaintext event content
*
* @return {module:client.Promise} Promise which resolves to the new event body
*/
/**
* Encrypt a message event
*
* @method module:crypto/algorithms/base.EncryptionAlgorithm.encryptMessage
* @abstract
*
* @param {module:models/room} room
* @param {string} eventType
* @param {object} plaintext event content
*
* @return {module:client.Promise} Promise which resolves to the new event body
*/
/**
* Called when the membership of a member of the room changes.
*
* @param {module:models/event.MatrixEvent} event event causing the change
* @param {module:models/room-member} member user whose membership changed
* @param {string=} oldMembership previous membership
*/
EncryptionAlgorithm.prototype.onRoomMembership = function(
event, member, oldMembership,
) {};
/**
* Called when the membership of a member of the room changes.
*
* @param {module:models/event.MatrixEvent} event event causing the change
* @param {module:models/room-member} member user whose membership changed
* @param {string=} oldMembership previous membership
* @public
*/
onRoomMembership(event, member, oldMembership) {
}
}
export {EncryptionAlgorithm}; // https://github.com/jsdoc3/jsdoc/issues/1272
/**
* base type for decryption implementations
*
* @constructor
* @alias module:crypto/algorithms/base.DecryptionAlgorithm
*
* @param {object} params parameters
@@ -101,55 +98,55 @@ EncryptionAlgorithm.prototype.onRoomMembership = function(
* @param {string=} params.roomId The ID of the room we will be receiving
* from. Null for to-device events.
*/
const DecryptionAlgorithm = function(params) {
this._userId = params.userId;
this._crypto = params.crypto;
this._olmDevice = params.olmDevice;
this._roomId = params.roomId;
};
/** */
module.exports.DecryptionAlgorithm = DecryptionAlgorithm;
class DecryptionAlgorithm {
constructor(params) {
this._userId = params.userId;
this._crypto = params.crypto;
this._olmDevice = params.olmDevice;
this._roomId = params.roomId;
}
/**
* Decrypt an event
*
* @method module:crypto/algorithms/base.DecryptionAlgorithm#decryptEvent
* @abstract
*
* @param {object} event raw event
*
* @return {null} if the event referred to an unknown megolm session
* @return {module:crypto.DecryptionResult} decryption result
*
* @throws {module:crypto/algorithms/base.DecryptionError} if there is a
* problem decrypting the event
*/
/**
* Decrypt an event
*
* @method module:crypto/algorithms/base.DecryptionAlgorithm#decryptEvent
* @abstract
*
* @param {object} event raw event
*
* @return {null} if the event referred to an unknown megolm session
* @return {module:crypto.DecryptionResult} decryption result
*
* @throws {module:crypto/algorithms/base.DecryptionError} if there is a
* problem decrypting the event
*/
/**
* Handle a key event
*
* @method module:crypto/algorithms/base.DecryptionAlgorithm#onRoomKeyEvent
*
* @param {module:models/event.MatrixEvent} params event key event
*/
DecryptionAlgorithm.prototype.onRoomKeyEvent = function(params) {
// ignore by default
};
/**
* Handle a key event
*
* @method module:crypto/algorithms/base.DecryptionAlgorithm#onRoomKeyEvent
*
* @param {module:models/event.MatrixEvent} params event key event
*/
onRoomKeyEvent(params) {
// ignore by default
}
/**
* Import a room key
*
* @param {module:crypto/OlmDevice.MegolmSessionData} session
*/
DecryptionAlgorithm.prototype.importRoomKey = function(session) {
// ignore by default
};
/**
* Import a room key
*
* @param {module:crypto/OlmDevice.MegolmSessionData} session
*/
importRoomKey(session) {
// ignore by default
}
}
export {DecryptionAlgorithm}; // https://github.com/jsdoc3/jsdoc/issues/1272
/**
* Exception thrown when decryption fails
*
* @alias module:crypto/algorithms/base.DecryptionError
* @constructor
* @param {string} msg user-visible message describing the problem
*
* @param {Object=} details key/value pairs reported in the logs but not shown
@@ -157,50 +154,51 @@ DecryptionAlgorithm.prototype.importRoomKey = function(session) {
*
* @extends Error
*/
const DecryptionError = function(msg, details) {
this.name = 'DecryptionError';
this.message = msg;
this.details = details;
};
utils.inherits(DecryptionError, Error);
/** override the string used when logging
*
* @returns {String}
*/
DecryptionError.prototype.toString = function() {
let result = this.name + '[msg: ' + this.message;
if (this.details) {
result += ', ' +
Object.keys(this.details).map(
(k) => k + ': ' + this.details[k],
).join(', ');
class DecryptionError extends Error {
constructor(msg, details) {
super(msg);
this.name = 'DecryptionError';
this.details = details;
}
result += ']';
/**
* override the string used when logging
*
* @returns {String}
*/
toString() {
let result = this.name + '[msg: ' + this.message;
return result;
};
if (this.details) {
result += ', ' +
Object.keys(this.details).map(
(k) => k + ': ' + this.details[k],
).join(', ');
}
module.exports.DecryptionError = DecryptionError;
result += ']';
return result;
}
}
export {DecryptionError}; // https://github.com/jsdoc3/jsdoc/issues/1272
/**
* Exception thrown specifically when we want to warn the user to consider
* the security of their conversation before continuing
*
* @constructor
* @param {string} msg message describing the problem
* @param {Object} devices userId -> {deviceId -> object}
* set of unknown devices per user we're warning about
* @extends Error
*/
module.exports.UnknownDeviceError = function(msg, devices) {
this.name = "UnknownDeviceError";
this.message = msg;
this.devices = devices;
};
utils.inherits(module.exports.UnknownDeviceError, Error);
export class UnknownDeviceError extends Error {
constructor(msg, devices) {
super(msg);
this.name = "UnknownDeviceError";
this.devices = devices;
}
}
/**
* Registers an encryption/decryption class for a particular algorithm
@@ -215,7 +213,7 @@ utils.inherits(module.exports.UnknownDeviceError, Error);
* module:crypto/algorithms/base.DecryptionAlgorithm|DecryptionAlgorithm}
* implementation
*/
module.exports.registerAlgorithm = function(algorithm, encryptor, decryptor) {
module.exports.ENCRYPTION_CLASSES[algorithm] = encryptor;
module.exports.DECRYPTION_CLASSES[algorithm] = decryptor;
};
export function registerAlgorithm(algorithm, encryptor, decryptor) {
ENCRYPTION_CLASSES[algorithm] = encryptor;
DECRYPTION_CLASSES[algorithm] = decryptor;
}
+6 -7
View File
@@ -1,5 +1,6 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -15,7 +16,6 @@ limitations under the License.
*/
"use strict";
/**
* @module crypto
*/
@@ -50,14 +50,18 @@ const DeviceList = require('./DeviceList').default;
* @param {string} deviceId The identifier for this device.
*
* @param {Object} clientStore the MatrixClient data store.
*
* @param {module:crypto/store/base~CryptoStore} cryptoStore
* storage for the crypto layer.
*/
function Crypto(baseApis, eventEmitter, sessionStore, userId, deviceId,
clientStore) {
clientStore, cryptoStore) {
this._baseApis = baseApis;
this._sessionStore = sessionStore;
this._userId = userId;
this._deviceId = deviceId;
this._clientStore = clientStore;
this._cryptoStore = cryptoStore;
this._olmDevice = new OlmDevice(sessionStore);
this._deviceList = new DeviceList(baseApis, sessionStore, this._olmDevice);
@@ -1121,11 +1125,6 @@ Crypto.prototype._signObject = function(obj) {
obj.signatures = sigs;
};
/**
* @see module:crypto/algorithms/base.DecryptionError
*/
Crypto.DecryptionError = algorithms.DecryptionError;
/** */
module.exports = Crypto;
+11
View File
@@ -0,0 +1,11 @@
/**
* Internal module. Defintions for storage for the crypto module
*
* @module
*/
/**
* Abstraction of things that can store data required for end-to-end encryption
*
* @interface CryptoStore
*/
+143
View File
@@ -0,0 +1,143 @@
/*
Copyright 2017 Vector Creations 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 q from 'q';
/**
* Internal module. indexeddb storage for e2e.
*
* @module
*/
const VERSION = 1;
/**
* @implements {module:crypto/store/base~CryptoStore}
*/
export default class IndexedDBCryptoStore {
/**
* Create a new IndexedDBCryptoStore
*
* @param {IDBFactory} indexedDB global indexedDB instance
* @param {string} dbName name of db to connect to
*/
constructor(indexedDB, dbName) {
if (!indexedDB) {
throw new Error("must pass indexedDB into IndexedDBCryptoStore");
}
this._indexedDB = indexedDB;
this._dbName = dbName;
this._dbPromise = null;
}
/**
* Ensure the database exists and is up-to-date
*
* @return {Promise} resolves to an instance of IDBDatabase when
* the database is ready
*/
connect() {
if (this._dbPromise) {
return this._dbPromise;
}
this._dbPromise = new q.Promise((resolve, reject) => {
console.log(`connecting to indexeddb ${this._dbName}`);
const req = this._indexedDB.open(this._dbName, VERSION);
req.onupgradeneeded = (ev) => {
const db = ev.target.result;
const oldVersion = ev.oldVersion;
console.log(
`Upgrading IndexedDBCryptoStore from version ${oldVersion}`
+ ` to ${VERSION}`,
);
if (oldVersion < 1) { // The database did not previously exist.
createDatabase(db);
}
// Expand as needed.
};
req.onblocked = () => {
reject(new Error(
"unable to upgrade indexeddb because it is open elsewhere",
));
};
req.onerror = (ev) => {
reject(new Error(
"unable to connect to indexeddb: " + ev.target.error,
));
};
req.onsuccess = (r) => {
const db = r.target.result;
// make sure we close the db on `onversionchange` - otherwise
// attempts to delete the database will block (and subsequent
// attempts to re-create it will also block).
db.onversionchange = (ev) => {
db.close();
};
resolve(db);
};
});
return this._dbPromise;
}
/**
* Delete all data from this store.
*
* @returns {Promise} resolves when the store has been cleared.
*/
deleteAllData() {
return new q.Promise((resolve, reject) => {
console.log(`Removing indexeddb instance: ${this._dbName}`);
const req = this._indexedDB.deleteDatabase(this._dbName);
req.onblocked = () => {
reject(new Error(
"unable to delete indexeddb because it is open elsewhere",
));
};
req.onerror = (ev) => {
reject(new Error(
"unable to delete indexeddb: " + ev.target.error,
));
};
req.onsuccess = () => {
console.log(`Removed indexeddb instance: ${this._dbName}`);
resolve();
};
});
}
}
function createDatabase(db) {
const outgoingRoomKeyRequestsStore =
db.createObjectStore("outgoingRoomKeyRequests", { keyPath: "requestId" });
// we assume that the RoomKeyRequestBody will have room_id and session_id
// properties, to make the index efficient.
outgoingRoomKeyRequestsStore.createIndex("session",
["requestBody.room_id", "requestBody.session_id"],
);
outgoingRoomKeyRequestsStore.createIndex("state", "state");
}
+40
View File
@@ -0,0 +1,40 @@
/*
Copyright 2017 Vector Creations 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 q from 'q';
/**
* Internal module. in-memory storage for e2e.
*
* @module
*/
/**
* @implements {module:crypto/store/base~CryptoStore}
*/
export default class MemoryCryptoStore {
constructor() {
}
/**
* Delete all data from this store.
*
* @returns {Promise} Promise which resolves when the store has been cleared.
*/
deleteAllData() {
return q();
}
}
+26
View File
@@ -63,6 +63,11 @@ module.exports.TimelineWindow = require("./timeline-window").TimelineWindow;
module.exports.InteractiveAuth = require("./interactive-auth");
module.exports.MemoryCryptoStore =
require("./crypto/store/memory-crypto-store").default;
module.exports.IndexedDBCryptoStore =
require("./crypto/store/indexeddb-crypto-store").default;
/**
* Create a new Matrix Call.
* @function
@@ -124,6 +129,19 @@ module.exports.wrapRequest = function(wrapper) {
};
};
let cryptoStoreFactory = () => new module.exports.MemoryCryptoStore;
/**
* Configure a different factory to be used for creating crypto stores
*
* @param {Function} fac a function which will return a new
* {@link module:crypto.store.base~CryptoStore}.
*/
module.exports.setCryptoStoreFactory = function(fac) {
cryptoStoreFactory = fac;
};
/**
* Construct a Matrix Client. Similar to {@link module:client~MatrixClient}
* except that the 'request', 'store' and 'scheduler' dependencies are satisfied.
@@ -136,6 +154,13 @@ module.exports.wrapRequest = function(wrapper) {
* {@link module:scheduler~MatrixScheduler}.
* @param {requestFunction} opts.request If not set, defaults to the function
* supplied to {@link request} which defaults to the request module from NPM.
*
* @param {module:crypto.store.base~CryptoStore=} opts.cryptoStore
* crypto store implementation. Calls the factory supplied to
* {@link setCryptoStoreFactory} if unspecified; or if no factory has been
* specified, uses a default implementation (indexeddb in the browser,
* in-memory otherwise).
*
* @return {MatrixClient} A new matrix client.
* @see {@link module:client~MatrixClient} for the full list of options for
* <code>opts</code>.
@@ -151,6 +176,7 @@ module.exports.createClient = function(opts) {
localStorage: global.localStorage,
});
opts.scheduler = opts.scheduler || new module.exports.MatrixScheduler();
opts.cryptoStore = opts.cryptoStore || cryptoStoreFactory();
return new module.exports.MatrixClient(opts);
};
+39 -1
View File
@@ -246,6 +246,45 @@ RoomState.prototype.getUserIdsWithDisplayName = function(displayName) {
return this._displayNameToUserIds[displayName] || [];
};
/**
* Returns true if userId is in room, event is not redacted and either sender of
* mxEvent or has power level sufficient to redact events other than their own.
* @param {MatrixEvent} mxEvent The event to test permission for
* @param {string} userId The user ID of the user to test permission for
* @return {boolean} true if the given used ID can redact given event
*/
RoomState.prototype.maySendRedactionForEvent = function(mxEvent, userId) {
const member = this.getMember(userId);
if (!member || member.membership === 'leave') return false;
if (mxEvent.status || mxEvent.isRedacted()) return false;
if (mxEvent.getSender() === userId) return true;
return this._hasSufficientPowerLevelFor('redact', member.powerLevel);
};
/**
* Returns true if the given power level is sufficient for action
* @param {string} action The type of power level to check
* @param {number} powerLevel The power level of the member
* @return {boolean} true if the given power level is sufficient
*/
RoomState.prototype._hasSufficientPowerLevelFor = function(action, powerLevel) {
const powerLevelsEvent = this.getStateEvents('m.room.power_levels', '');
let powerLevels = {};
if (powerLevelsEvent) {
powerLevels = powerLevelsEvent.getContent();
}
let requiredLevel = 50;
if (powerLevels[action] !== undefined) {
requiredLevel = powerLevels[action];
}
return powerLevel >= requiredLevel;
};
/**
* Short-form for maySendEvent('m.room.message', userId)
* @param {string} userId The user ID of the user to test permission for
@@ -269,7 +308,6 @@ RoomState.prototype.maySendEvent = function(eventType, userId) {
return this._maySendEventOfType(eventType, userId, false);
};
/**
* Returns true if the given MatrixClient has permission to send a state
* event of type `stateEventType` into this room.
+1 -19
View File
@@ -27,6 +27,7 @@ const ContentRepo = require("../content-repo");
const EventTimeline = require("./event-timeline");
const EventTimelineSet = require("./event-timeline-set");
import reEmit from '../reemit';
function synthesizeReceipt(userId, event, receiptType) {
// console.log("synthesizing receipt for "+event.getId());
@@ -1252,25 +1253,6 @@ function calculateRoomName(room, userId, ignoreRoomNameEvent) {
}
}
// FIXME: copypasted from sync.js
function reEmit(reEmitEntity, emittableEntity, eventNames) {
utils.forEach(eventNames, function(eventName) {
// setup a listener on the entity (the Room, User, etc) for this event
emittableEntity.on(eventName, function() {
// take the args from the listener and reuse them, adding the
// event name to the arg list so it works with .emit()
// Transformation Example:
// listener on "foo" => function(a,b) { ... }
// Re-emit on "thing" => thing.emit("foo", a, b)
const newArgs = [eventName];
for (let i = 0; i < arguments.length; i++) {
newArgs.push(arguments[i]);
}
reEmitEntity.emit(...newArgs);
});
});
}
/**
* The Room class.
*/
+44
View File
@@ -0,0 +1,44 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations 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
*/
/**
* re-emit events raised by one EventEmitter from another
*
* @param {external:EventEmitter} reEmitEntity
* entity from which we want events to be emitted
* @param {external:EventEmitter} emittableEntity
* entity from which events are currently emitted
* @param {Array<string>} eventNames
* list of events to be reemitted
*/
export default function reEmit(reEmitEntity, emittableEntity, eventNames) {
for (const eventName of eventNames) {
// setup a listener on the entity (the Room, User, etc) for this event
emittableEntity.on(eventName, function(...args) {
// take the args from the listener and reuse them, adding the
// event name to the arg list so it works with .emit()
// Transformation Example:
// listener on "foo" => function(a,b) { ... }
// Re-emit on "thing" => thing.emit("foo", a, b)
reEmitEntity.emit(eventName, ...args);
});
}
}
+2 -18
View File
@@ -31,6 +31,8 @@ const utils = require("./utils");
const Filter = require("./filter");
const EventTimeline = require("./models/event-timeline");
import reEmit from './reemit';
const DEBUG = true;
// /sync requests allow you to set a timeout= but the request may continue
@@ -1252,23 +1254,5 @@ function createNewUser(client, userId) {
return user;
}
function reEmit(reEmitEntity, emittableEntity, eventNames) {
utils.forEach(eventNames, function(eventName) {
// setup a listener on the entity (the Room, User, etc) for this event
emittableEntity.on(eventName, function() {
// take the args from the listener and reuse them, adding the
// event name to the arg list so it works with .emit()
// Transformation Example:
// listener on "foo" => function(a,b) { ... }
// Re-emit on "thing" => thing.emit("foo", a, b)
const newArgs = [eventName];
for (let i = 0; i < arguments.length; i++) {
newArgs.push(arguments[i]);
}
reEmitEntity.emit(...newArgs);
});
});
}
/** */
module.exports = SyncApi;
+7 -6
View File
@@ -1219,6 +1219,8 @@ const _getScreenSharingConstraints = function(call) {
};
const _getUserMediaVideoContraints = function(callType) {
const isWebkit = !!global.window.navigator.webkitGetUserMedia;
switch (callType) {
case 'voice':
return {
@@ -1232,12 +1234,11 @@ const _getUserMediaVideoContraints = function(callType) {
deviceId: audioInput ? {exact: audioInput} : undefined,
}, video: {
deviceId: videoInput ? {exact: videoInput} : undefined,
mandatory: {
minWidth: 640,
maxWidth: 640,
minHeight: 360,
maxHeight: 360,
},
/* We want 640x360. Chrome will give it only if we ask exactly,
FF refuses entirely if we ask exactly, so have to ask for ideal
instead */
width: isWebkit ? { exact: 640 } : { ideal: 640 },
height: isWebkit ? { exact: 360 } : { ideal: 360 },
},
};
}