From fb1b554b862677848e94dc0e1cf74e6ea184419a Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Mon, 15 Jan 2018 01:50:24 +0000 Subject: [PATCH 01/70] initial pseudocode WIP for e2e online backups --- src/client.js | 1 + src/crypto/OlmDevice.js | 20 ++++++++++++++++++++ src/crypto/index.js | 14 ++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/src/client.js b/src/client.js index 0b00ab36b9e..29357118537 100644 --- a/src/client.js +++ b/src/client.js @@ -393,6 +393,7 @@ MatrixClient.prototype.initCrypto = async function() { "crypto.roomKeyRequest", "crypto.roomKeyRequestCancellation", "crypto.warning", + "crypto.suggestKeyRestore", ]); await crypto.init(); diff --git a/src/crypto/OlmDevice.js b/src/crypto/OlmDevice.js index cda14779c2b..131419ea7e7 100644 --- a/src/crypto/OlmDevice.js +++ b/src/crypto/OlmDevice.js @@ -91,6 +91,21 @@ function OlmDevice(sessionStore, cryptoStore) { this.deviceEd25519Key = null; this._maxOneTimeKeys = null; + // track whether this device's megolm keys are being backed up incrementally + // to the server or not. + // XXX: this should probably have a single source of truth from OlmAccount + this.backupKey = null; + + // track which of our other devices (if any) have cross-signed this device + // XXX: this should probably have a single source of truth in the /devices + // API store or whatever we use to track our self-signed devices. + this.crossSelfSigs = []; + + // track whether we have already suggested to the user that they should + // restore their keys from backup or by cross-signing the device. + // We use this to avoid repeatedly emitting the suggestion event. + this.suggestedKeyRestore = false; + // we don't bother stashing outboundgroupsessions in the sessionstore - // instead we keep them here. this._outboundGroupSessionStore = {}; @@ -921,6 +936,11 @@ OlmDevice.prototype.addInboundGroupSession = async function( this._cryptoStore.addEndToEndInboundGroupSession( senderKey, sessionId, sessionData, txn, ); + + if (this.backupKey) { + // get olm::Account::generate_backup_encryption_secret + // save sessionData (pickled with this secret) to the server + } } finally { session.free(); } diff --git a/src/crypto/index.js b/src/crypto/index.js index 6b1d8f477f6..31dcd76b656 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -1015,6 +1015,13 @@ Crypto.prototype._onRoomKeyEvent = function(event) { return; } + if (!device.suggestedKeyRestore && + !device.backupKey && !device.selfCrossSigs.length) + { + this.emit("crypto.suggestKeyRestore"); + device.suggestKeyRestore = true; + } + const alg = this._getRoomDecryptor(content.room_id, content.algorithm); alg.onRoomKeyEvent(event); }; @@ -1355,6 +1362,13 @@ class IncomingRoomKeyRequestCancellation { * @param {module:crypto~IncomingRoomKeyRequestCancellation} req */ +/** + * Fires when we want to suggest to the user that they restore their megolm keys + * from backup or by cross-signing the device. + * + * @event module:client~MatrixClient#"crypto.suggestKeyRestore" + */ + /** * Fires when the app may wish to warn the user about something related * the end-to-end crypto. From e0c9b990e7db19bed24129b9307b37ed4600c765 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Thu, 18 Jan 2018 20:59:08 +0000 Subject: [PATCH 02/70] blindly move crypto.suggestKeyRestore over to /sync --- src/client.js | 7 ++++++- src/crypto/OlmDevice.js | 1 + src/crypto/index.js | 14 -------------- src/sync.js | 10 ++++++++++ 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/client.js b/src/client.js index 29357118537..aa05462ac1c 100644 --- a/src/client.js +++ b/src/client.js @@ -393,7 +393,6 @@ MatrixClient.prototype.initCrypto = async function() { "crypto.roomKeyRequest", "crypto.roomKeyRequestCancellation", "crypto.warning", - "crypto.suggestKeyRestore", ]); await crypto.init(); @@ -3630,6 +3629,12 @@ module.exports.CRYPTO_ENABLED = CRYPTO_ENABLED; * }); */ +/** + * Fires when we want to suggest to the user that they restore their megolm keys + * from backup or by cross-signing the device. + * + * @event module:client~MatrixClient#"crypto.suggestKeyRestore" + */ // EventEmitter JSDocs diff --git a/src/crypto/OlmDevice.js b/src/crypto/OlmDevice.js index 131419ea7e7..656e4fddc05 100644 --- a/src/crypto/OlmDevice.js +++ b/src/crypto/OlmDevice.js @@ -104,6 +104,7 @@ function OlmDevice(sessionStore, cryptoStore) { // track whether we have already suggested to the user that they should // restore their keys from backup or by cross-signing the device. // We use this to avoid repeatedly emitting the suggestion event. + // XXX: persist this somewhere! this.suggestedKeyRestore = false; // we don't bother stashing outboundgroupsessions in the sessionstore - diff --git a/src/crypto/index.js b/src/crypto/index.js index 31dcd76b656..6b1d8f477f6 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -1015,13 +1015,6 @@ Crypto.prototype._onRoomKeyEvent = function(event) { return; } - if (!device.suggestedKeyRestore && - !device.backupKey && !device.selfCrossSigs.length) - { - this.emit("crypto.suggestKeyRestore"); - device.suggestKeyRestore = true; - } - const alg = this._getRoomDecryptor(content.room_id, content.algorithm); alg.onRoomKeyEvent(event); }; @@ -1362,13 +1355,6 @@ class IncomingRoomKeyRequestCancellation { * @param {module:crypto~IncomingRoomKeyRequestCancellation} req */ -/** - * Fires when we want to suggest to the user that they restore their megolm keys - * from backup or by cross-signing the device. - * - * @event module:client~MatrixClient#"crypto.suggestKeyRestore" - */ - /** * Fires when the app may wish to warn the user about something related * the end-to-end crypto. diff --git a/src/sync.js b/src/sync.js index 71fb866d523..74aff54bf08 100644 --- a/src/sync.js +++ b/src/sync.js @@ -1,6 +1,7 @@ /* Copyright 2015, 2016 OpenMarket Ltd Copyright 2017 Vector Creations Ltd +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. @@ -983,6 +984,15 @@ SyncApi.prototype._processSyncResponse = async function( async function processRoomEvent(e) { client.emit("event", e); if (e.isState() && e.getType() == "m.room.encryption" && self.opts.crypto) { + + // XXX: get device + if (!device.getSuggestedKeyRestore() && + !device.backupKey && !device.selfCrossSigs.length) + { + client.emit("crypto.suggestKeyRestore"); + device.setSuggestedKeyRestore(true); + } + await self.opts.crypto.onCryptoEvent(e); } } From d55618921bb584164417297162ab22ba0df08564 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Tue, 7 Aug 2018 23:10:55 -0400 Subject: [PATCH 03/70] initial implementation of e2e key backup and restore --- spec/unit/crypto/backup.spec.js | 218 ++++++++++++++++++++++++++++++++ src/client.js | 84 ++++++++++++ src/crypto/OlmDevice.js | 10 -- src/crypto/algorithms/megolm.js | 56 +++++++- src/crypto/index.js | 5 + src/sync.js | 4 +- 6 files changed, 365 insertions(+), 12 deletions(-) create mode 100644 spec/unit/crypto/backup.spec.js diff --git a/spec/unit/crypto/backup.spec.js b/spec/unit/crypto/backup.spec.js new file mode 100644 index 00000000000..b6ec6dce585 --- /dev/null +++ b/spec/unit/crypto/backup.spec.js @@ -0,0 +1,218 @@ +try { + global.Olm = require('olm'); +} catch (e) { + console.warn("unable to run megolm backup tests: libolm not available"); +} + +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 MemoryCryptoStore from '../../../lib/crypto/store/memory-crypto-store.js'; +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 MatrixClient = sdk.MatrixClient; +const MatrixEvent = sdk.MatrixEvent; +const MegolmDecryption = algorithms.DECRYPTION_CLASSES['m.megolm.v1.aes-sha2']; + +const ROOM_ID = '!ROOM:ID'; + +describe("MegolmBackup", function() { + if (!global.Olm) { + console.warn('Not running megolm backup unit tests: libolm not present'); + return; + } + + let olmDevice; + let mockOlmLib; + let mockCrypto; + let mockStorage; + let sessionStore; + let cryptoStore; + let megolmDecryption; + beforeEach(function () { + testUtils.beforeEach(this); // eslint-disable-line no-invalid-this + + mockCrypto = testUtils.mock(Crypto, 'Crypto'); + mockCrypto.backupKey = new Olm.PkEncryption(); + mockCrypto.backupKey.set_recipient_key("hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmoK"); + + mockStorage = new MockStorageApi(); + sessionStore = new WebStorageSessionStore(mockStorage); + cryptoStore = new MemoryCryptoStore(mockStorage); + + olmDevice = new OlmDevice(sessionStore, cryptoStore); + + // we stub out the olm encryption bits + mockOlmLib = {}; + mockOlmLib.ensureOlmSessionsForDevices = expect.createSpy(); + mockOlmLib.encryptMessageForDevice = + expect.createSpy().andReturn(Promise.resolve()); + }); + + describe("backup", function() { + let mockBaseApis; + + beforeEach(function() { + mockBaseApis = {}; + + megolmDecryption = new MegolmDecryption({ + userId: '@user:id', + crypto: mockCrypto, + olmDevice: olmDevice, + baseApis: mockBaseApis, + roomId: ROOM_ID, + }); + + megolmDecryption.olmlib = mockOlmLib; + }); + + it('automatically backs up keys', function() { + const groupSession = new global.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); + }; + + const sessionId = groupSession.session_id(); + const cipherText = groupSession.encrypt(JSON.stringify({ + room_id: ROOM_ID, + content: 'testytest', + })); + const msgevent = new MatrixEvent({ + type: 'm.room.encrypted', + room_id: ROOM_ID, + content: { + algorithm: 'm.megolm.v1.aes-sha2', + sender_key: "SENDER_CURVE25519", + session_id: sessionId, + ciphertext: cipherText, + }, + event_id: "$event1", + origin_server_ts: 1507753886000, + }); + + mockBaseApis.sendKeyBackup = expect.createSpy(); + + return event.attemptDecryption(mockCrypto).then(() => { + return megolmDecryption.onRoomKeyEvent(event); + }).then(() => { + expect(mockBaseApis.sendKeyBackup).toHaveBeenCalled(); + }); + }); + }); + + describe("restore", function () { + let client; + + beforeEach(function() { + const scheduler = [ + "getQueueForEvent", "queueEvent", "removeEventFromQueue", + "setProcessFunction", + ].reduce((r, k) => { r[k] = expect.createSpy(); 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] = expect.createSpy(); return r; }, {}); + store.getSavedSync = expect.createSpy().andReturn(Promise.resolve(null)); + store.getSavedSyncToken = expect.createSpy().andReturn(Promise.resolve(null)); + store.setSyncData = expect.createSpy().andReturn(Promise.resolve(null)); + 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(); + }); + + it('can restore from backup', function () { + const event = new MatrixEvent({ + type: 'm.room.encrypted', + room_id: '!ROOM:ID', + content: { + algorithm: 'm.megolm.v1.aes-sha2', + sender_key: 'SENDER_CURVE25519', + session_id: 'o+21hSjP+mgEmcfdslPsQdvzWnkdt0Wyo00Kp++R8Kc', + ciphertext: 'AwgAEjD+VwXZ7PoGPRS/H4kwpAsMp/g+WPvJVtPEKE8fmM9IcT/NCiwPb8PehecDKP0cjm1XO88k6Bw3D17aGiBHr5iBoP7oSw8CXULXAMTkBlmkufRQq2+d0Giy1s4/Cg5n13jSVrSb2q7VTSv1ZHAFjUCsLSfR0gxqcQs' + }, + event_id: '$event1', + origin_server_ts: 1507753886000, + }); + client._http.authedRequest = function () { + return Promise.resolve({ + data: { + first_message_index: 0, + forwarded_count: 0, + is_verified: false, + session_data: { + ciphertext: '2z2M7CZ+azAiTHN1oFzZ3smAFFt+LEOYY6h3QO3XXGdw6YpNn/gpHDO6I/rgj1zNd4FoTmzcQgvKdU8kN20u5BWRHxaHTZSlne5RxE6vUdREsBgZePglBNyG0AogR/PVdcrv/v18Y6rLM5O9SELmwbV63uV9Kuu/misMxoqbuqEdG7uujyaEKtjlQsJ5MGPQOySyw7XrnesSwF6XWRMxcPGRV0xZr3s9PI350Wve3EncjRgJ9IGFru1bcptMqfXgPZkOyGvrphHoFfoK7nY3xMEHUiaTRfRIjq8HNV4o8QY1qmWGnxNBQgOlL8MZlykjg3ULmQ3DtFfQPj/YYGS3jzxvC+EBjaafmsg+52CTeK3Rswu72PX450BnSZ1i3If4xWAUKvjTpeUg5aDLqttOv1pITolTJDw5W/SD+b5rjEKg1CFCHGEGE9wwV3NfQHVCQL+dfpd7Or0poy4dqKMAi3g0o3Tg7edIF8d5rREmxaALPyiie8PHD8mj/5Y0GLqrac4CD6+Mop7eUTzVovprjg', + mac: '5lxYBHQU80M', + ephemeral: '/Bn0A4UMFwJaDDvh0aEk1XZj3k1IfgCxgFY9P9a0b14', + } + }, + headers: {}, + code: 200 + }); + }; + const decryption = new Olm.PkDecryption(); + decryption.unpickle("secret_key", "qx37WTQrjZLz5tId/uBX9B3/okqAbV1ofl9UnHKno1eipByCpXleAAlAZoJgYnCDOQZDQWzo3luTSfkF9pU1mOILCbbouubs6TVeDyPfgGD9i86J8irHjA"); + return client.restoreKeyBackups(decryption, ROOM_ID, 'o+21hSjP+mgEmcfdslPsQdvzWnkdt0Wyo00Kp++R8Kc') + .then(() => { + return megolmDecryption.decryptEvent(event); + }).then((res) => { + expect(res.clearEvent.content).toEqual('testytest'); + }); + }); + }); +}); diff --git a/src/client.js b/src/client.js index 2a0002df557..7aa40d09d93 100644 --- a/src/client.js +++ b/src/client.js @@ -703,6 +703,90 @@ MatrixClient.prototype.importRoomKeys = function(keys) { return this._crypto.importRoomKeys(keys); }; +MatrixClient.prototype._makeKeyBackupPath = function(roomId, sessionId, version) { + let path; + if (sessionId !== undefined) { + path = utils.encodeUri("/room_keys/keys/$roomId/$sessionId", { + $roomId: roomId, + $sessionId: sessionId, + }); + } else if (roomId !== undefined) { + path = utils.encodeUri("/room_keys/keys/$roomId", { + $roomId: roomId, + }); + } else { + path = "/room_keys/keys"; + } + const queryData = version === undefined ? undefined : {version : version}; + return { + path: path, + queryData: queryData, + } +} + +/** + * Back up session keys to the homeserver. + * @param {string} roomId ID of the room that the keys are for Optional. + * @param {string} sessionId ID of the session that the keys are for Optional. + * @param {integer} version backup version Optional. + * @param {object} key data + * @param {module:client.callback} callback Optional. + * @return {module:client.Promise} a promise that will resolve when the keys + * are uploaded + */ +MatrixClient.prototype.sendKeyBackup = function(roomId, sessionId, version, data, callback) { + if (this._crypto === null) { + throw new Error("End-to-end encryption disabled"); + } + + const path = this._makeKeyBackupPath(roomId, sessionId, version); + return this._http.authedRequest( + callback, "PUT", path.path, path.queryData, data, + ); +}; + +MatrixClient.prototype.restoreKeyBackups = function(decryptionKey, roomId, sessionId, version, callback) { + if (this._crypto === null) { + throw new Error("End-to-end encryption disabled"); + } + + const path = this._makeKeyBackupPath(roomId, sessionId, version); + return this._http.authedRequest( + undefined, "GET", path.path, path.queryData, + ).then((response) => { + if (response.code === 200) { + const keys = []; + // FIXME: for each room, session, if response has multiple + // decrypt response.data.session_data + const data = response.data; + const key = JSON.parse(decryptionKey.decrypt(data.session_data.ephemeral, data.session_data.mac, data.session_data.ciphertext)); + // set room_id and session_id + key.room_id = roomId; + key.session_id = sessionId; + keys.push(key); + return this.importRoomKeys(keys); + } else { + callback("aargh!"); + return Promise.reject("aaargh!"); + } + }).then(() => { + if (callback) { + callback(); + } + }) +}; + +MatrixClient.prototype.deleteKeyBackups = function(roomId, sessionId, version, callback) { + if (this._crypto === null) { + throw new Error("End-to-end encryption disabled"); + } + + const path = this._makeKeyBackupPath(roomId, sessionId, version); + return this._http.authedRequest( + callback, "DELETE", path.path, path.queryData, + ) +}; + // Group ops // ========= // Operations on groups that come down the sync stream (ie. ones the diff --git a/src/crypto/OlmDevice.js b/src/crypto/OlmDevice.js index 656e4fddc05..950faa8e2a4 100644 --- a/src/crypto/OlmDevice.js +++ b/src/crypto/OlmDevice.js @@ -91,11 +91,6 @@ function OlmDevice(sessionStore, cryptoStore) { this.deviceEd25519Key = null; this._maxOneTimeKeys = null; - // track whether this device's megolm keys are being backed up incrementally - // to the server or not. - // XXX: this should probably have a single source of truth from OlmAccount - this.backupKey = null; - // track which of our other devices (if any) have cross-signed this device // XXX: this should probably have a single source of truth in the /devices // API store or whatever we use to track our self-signed devices. @@ -937,11 +932,6 @@ OlmDevice.prototype.addInboundGroupSession = async function( this._cryptoStore.addEndToEndInboundGroupSession( senderKey, sessionId, sessionData, txn, ); - - if (this.backupKey) { - // get olm::Account::generate_backup_encryption_secret - // save sessionData (pickled with this secret) to the server - } } finally { session.free(); } diff --git a/src/crypto/algorithms/megolm.js b/src/crypto/algorithms/megolm.js index 005d157c548..b246fc141eb 100644 --- a/src/crypto/algorithms/megolm.js +++ b/src/crypto/algorithms/megolm.js @@ -804,7 +804,7 @@ MegolmDecryption.prototype.onRoomKeyEvent = function(event) { } console.log(`Adding key for megolm session ${senderKey}|${sessionId}`); - this._olmDevice.addInboundGroupSession( + return this._olmDevice.addInboundGroupSession( content.room_id, senderKey, forwardingKeyChain, sessionId, content.session_key, keysClaimed, exportFormat, @@ -819,6 +819,12 @@ MegolmDecryption.prototype.onRoomKeyEvent = function(event) { // have another go at decrypting events sent with this session. this._retryDecryption(senderKey, sessionId); + }).then(() => { + return this.backupGroupSession( + content.room_id, senderKey, forwardingKeyChain, + content.session_id, content.session_key, keysClaimed, + exportFormat, + ); }).catch((e) => { console.error(`Error handling m.room_key_event: ${e}`); }); @@ -941,6 +947,54 @@ MegolmDecryption.prototype.importRoomKey = function(session) { }); }; +MegolmDecryption.prototype.backupGroupSession = async function( + roomId, senderKey, forwardingCurve25519KeyChain, + sessionId, sessionKey, keysClaimed, + exportFormat, +) { + // new session. + const session = new Olm.InboundGroupSession(); + let first_known_index; + try { + if (exportFormat) { + session.import_session(sessionKey); + } else { + session.create(sessionKey); + } + if (sessionId != session.session_id()) { + throw new Error( + "Mismatched group session ID from senderKey: " + + senderKey, + ); + } + + if (!exportFormat) { + sessionKey = session.export_session(); + } + const first_known_index = session.first_known_index(); + + const sessionData = { + algorithm: olmlib.MEGOLM_ALGORITHM, + sender_key: senderKey, + sender_claimed_keys: keysClaimed, + forwardingCurve25519KeyChain: forwardingCurve25519KeyChain, + session_key: sessionKey + }; + const encrypted = this._crypto.backupKey.encrypt(JSON.stringify(sessionData)); + const data = { + first_message_index: first_known_index, + forwarded_count: forwardingCurve25519KeyChain.length, + is_verified: false, // FIXME: how do we determine this? + session_data: encrypted + }; + return this._baseApis.sendKeyBackup(roomId, sessionId, data); + } catch (e) { + return Promise.reject(e); + } finally { + session.free(); + } +} + /** * Have another go at decrypting events after we receive a key * diff --git a/src/crypto/index.js b/src/crypto/index.js index 703bd063164..fe233f2cfca 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -72,6 +72,11 @@ function Crypto(baseApis, sessionStore, userId, deviceId, this._cryptoStore = cryptoStore; this._roomList = roomList; + // track whether this device's megolm keys are being backed up incrementally + // to the server or not. + // XXX: this should probably have a single source of truth from OlmAccount + this.backupKey = null; + this._olmDevice = new OlmDevice(sessionStore, cryptoStore); this._deviceList = new DeviceList( baseApis, cryptoStore, sessionStore, this._olmDevice, diff --git a/src/sync.js b/src/sync.js index b85f838f625..7c769f20e04 100644 --- a/src/sync.js +++ b/src/sync.js @@ -1060,13 +1060,15 @@ SyncApi.prototype._processSyncResponse = async function( client.emit("event", e); if (e.isState() && e.getType() == "m.room.encryption" && self.opts.crypto) { + /* // XXX: get device - if (!device.getSuggestedKeyRestore() && + if (!device.getSuggestedKeyRestore() && !device.backupKey && !device.selfCrossSigs.length) { client.emit("crypto.suggestKeyRestore"); device.setSuggestedKeyRestore(true); } + */ await self.opts.crypto.onCryptoEvent(e); } From 1faf4775373608ab67c5613a9ff58bda6179c93b Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Wed, 22 Aug 2018 23:58:59 -0400 Subject: [PATCH 04/70] fix formatting and fix authedRequest usage --- spec/unit/crypto/backup.spec.js | 63 +++++++++++++++++++++------------ src/client.js | 31 ++++++++-------- 2 files changed, 55 insertions(+), 39 deletions(-) diff --git a/spec/unit/crypto/backup.spec.js b/spec/unit/crypto/backup.spec.js index b6ec6dce585..a4956647fe1 100644 --- a/spec/unit/crypto/backup.spec.js +++ b/spec/unit/crypto/backup.spec.js @@ -45,8 +45,10 @@ describe("MegolmBackup", function() { testUtils.beforeEach(this); // eslint-disable-line no-invalid-this mockCrypto = testUtils.mock(Crypto, 'Crypto'); - mockCrypto.backupKey = new Olm.PkEncryption(); - mockCrypto.backupKey.set_recipient_key("hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmoK"); + mockCrypto.backupKey = new global.Olm.PkEncryption(); + mockCrypto.backupKey.set_recipient_key( + "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmoK" + ); mockStorage = new MockStorageApi(); sessionStore = new WebStorageSessionStore(mockStorage); @@ -184,35 +186,50 @@ describe("MegolmBackup", function() { algorithm: 'm.megolm.v1.aes-sha2', sender_key: 'SENDER_CURVE25519', session_id: 'o+21hSjP+mgEmcfdslPsQdvzWnkdt0Wyo00Kp++R8Kc', - ciphertext: 'AwgAEjD+VwXZ7PoGPRS/H4kwpAsMp/g+WPvJVtPEKE8fmM9IcT/NCiwPb8PehecDKP0cjm1XO88k6Bw3D17aGiBHr5iBoP7oSw8CXULXAMTkBlmkufRQq2+d0Giy1s4/Cg5n13jSVrSb2q7VTSv1ZHAFjUCsLSfR0gxqcQs' + ciphertext: 'AwgAEjD+VwXZ7PoGPRS/H4kwpAsMp/g+WPvJVtPEKE8fmM9IcT/N' + + 'CiwPb8PehecDKP0cjm1XO88k6Bw3D17aGiBHr5iBoP7oSw8CXULXAMTkBl' + + 'mkufRQq2+d0Giy1s4/Cg5n13jSVrSb2q7VTSv1ZHAFjUCsLSfR0gxqcQs' }, event_id: '$event1', origin_server_ts: 1507753886000, }); client._http.authedRequest = function () { return Promise.resolve({ - data: { - first_message_index: 0, - forwarded_count: 0, - is_verified: false, - session_data: { - ciphertext: '2z2M7CZ+azAiTHN1oFzZ3smAFFt+LEOYY6h3QO3XXGdw6YpNn/gpHDO6I/rgj1zNd4FoTmzcQgvKdU8kN20u5BWRHxaHTZSlne5RxE6vUdREsBgZePglBNyG0AogR/PVdcrv/v18Y6rLM5O9SELmwbV63uV9Kuu/misMxoqbuqEdG7uujyaEKtjlQsJ5MGPQOySyw7XrnesSwF6XWRMxcPGRV0xZr3s9PI350Wve3EncjRgJ9IGFru1bcptMqfXgPZkOyGvrphHoFfoK7nY3xMEHUiaTRfRIjq8HNV4o8QY1qmWGnxNBQgOlL8MZlykjg3ULmQ3DtFfQPj/YYGS3jzxvC+EBjaafmsg+52CTeK3Rswu72PX450BnSZ1i3If4xWAUKvjTpeUg5aDLqttOv1pITolTJDw5W/SD+b5rjEKg1CFCHGEGE9wwV3NfQHVCQL+dfpd7Or0poy4dqKMAi3g0o3Tg7edIF8d5rREmxaALPyiie8PHD8mj/5Y0GLqrac4CD6+Mop7eUTzVovprjg', - mac: '5lxYBHQU80M', - ephemeral: '/Bn0A4UMFwJaDDvh0aEk1XZj3k1IfgCxgFY9P9a0b14', - } - }, - headers: {}, - code: 200 + 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 decryption = new Olm.PkDecryption(); - decryption.unpickle("secret_key", "qx37WTQrjZLz5tId/uBX9B3/okqAbV1ofl9UnHKno1eipByCpXleAAlAZoJgYnCDOQZDQWzo3luTSfkF9pU1mOILCbbouubs6TVeDyPfgGD9i86J8irHjA"); - return client.restoreKeyBackups(decryption, ROOM_ID, 'o+21hSjP+mgEmcfdslPsQdvzWnkdt0Wyo00Kp++R8Kc') - .then(() => { - return megolmDecryption.decryptEvent(event); - }).then((res) => { - expect(res.clearEvent.content).toEqual('testytest'); - }); + const decryption = new global.Olm.PkDecryption(); + decryption.unpickle( + "secret_key", + "qx37WTQrjZLz5tId/uBX9B3/okqAbV1ofl9UnHKno1eipByCpXleAAlAZoJgYnCDOQZD" + + "QWzo3luTSfkF9pU1mOILCbbouubs6TVeDyPfgGD9i86J8irHjA" + ); + return client.restoreKeyBackups( + decryption, + ROOM_ID, + 'o+21hSjP+mgEmcfdslPsQdvzWnkdt0Wyo00Kp++R8Kc' + ).then(() => { + return megolmDecryption.decryptEvent(event); + }).then((res) => { + expect(res.clearEvent.content).toEqual('testytest'); + }); }); }); }); diff --git a/src/client.js b/src/client.js index 7aa40d09d93..329a58498f6 100644 --- a/src/client.js +++ b/src/client.js @@ -753,22 +753,21 @@ MatrixClient.prototype.restoreKeyBackups = function(decryptionKey, roomId, sessi const path = this._makeKeyBackupPath(roomId, sessionId, version); return this._http.authedRequest( undefined, "GET", path.path, path.queryData, - ).then((response) => { - if (response.code === 200) { - const keys = []; - // FIXME: for each room, session, if response has multiple - // decrypt response.data.session_data - const data = response.data; - const key = JSON.parse(decryptionKey.decrypt(data.session_data.ephemeral, data.session_data.mac, data.session_data.ciphertext)); - // set room_id and session_id - key.room_id = roomId; - key.session_id = sessionId; - keys.push(key); - return this.importRoomKeys(keys); - } else { - callback("aargh!"); - return Promise.reject("aaargh!"); - } + ).then((res) => { + const keys = []; + // FIXME: for each room, session, if response has multiple + // decrypt response.data.session_data + const session_data = res.session_data; + const key = JSON.parse(decryptionKey.decrypt( + session_data.ephemeral, + session_data.mac, + session_data.ciphertext + )); + // set room_id and session_id + key.room_id = roomId; + key.session_id = sessionId; + keys.push(key); + return this.importRoomKeys(keys); }).then(() => { if (callback) { callback(); From fb8efe368a2538d294af9aee3135bc6e9789a61c Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Thu, 23 Aug 2018 00:03:36 -0400 Subject: [PATCH 05/70] initial draft of API for working with backup versions --- src/client.js | 68 ++++++++++++++++++++++++++++++++++++++++++++ src/crypto/olmlib.js | 5 ++++ 2 files changed, 73 insertions(+) diff --git a/src/client.js b/src/client.js index 329a58498f6..e9f651d49a4 100644 --- a/src/client.js +++ b/src/client.js @@ -703,6 +703,74 @@ MatrixClient.prototype.importRoomKeys = function(keys) { return this._crypto.importRoomKeys(keys); }; +/** + * Get information about the current key backup. + */ +MatrixClient.prototype.getKeyBackupVersion = function(callback) { + return this._http.authedRequest( + undefined, "GET", "/room_keys/version", + ).then((res) => { + if (res.algorithm !== olmlib.MEGOLM_BACKUP_ALGORITHM) { + const err = "Unknown backup algorithm: " + res.algorithm; + callback(err); + return Promise.reject(err); + } else if (!(typeof res.auth_data === "object") + || !res.auth_data.public_key) { + const err = "Invalid backup data returned"; + callback(err); + return Promise.reject(err); + } else { + if (callback) { + callback(null, res); + } + return res; + } + }); +} + +/** + * Enable backing up of keys, using data previously returned from + * getKeyBackupVersion. + */ +MatrixClient.prototype.enableKeyBackup = function(info) { + this._crypto.backupKey = new global.Olm.PkEncryption(); + this._crypto.backupKey.set_recipient_key(info.auth_data.public_key); +} + +/** + * Disable backing up of keys. + */ +MatrixClient.prototype.disableKeyBackup = function() { + this._crypto.backupKey = undefined; +} + +/** + * Create a new key backup version and enable it. + */ +MatrixClient.prototype.createKeyBackupVersion = function(callback) { + const decryption = new global.Olm.PkDecryption(); + const public_key = decryption.generate_key(); + const encryption = new global.Olm.PkEncryption(); + encryption.set_recipient_key(public_key); + const data = { + algorithm: olmlib.MEGOLM_BACKUP_ALGORITHM, + auth_data: { + public_key: public_key, + } + }; + this._crypto._signObject(data.auth_data); + return this._http.authedRequest( + undefined, "POST", "/room_keys/version", undefined, data, + ).then((res) => { + this._crypto.backupKey = encryption; + // FIXME: pickle isn't the right thing to use, but we don't have + // anything else yet + const recovery_key = decryption.pickle(""); + callback(null, recovery_key); + return recovery_key; + }); +} + MatrixClient.prototype._makeKeyBackupPath = function(roomId, sessionId, version) { let path; if (sessionId !== undefined) { diff --git a/src/crypto/olmlib.js b/src/crypto/olmlib.js index 56799c5132c..f03714f1644 100644 --- a/src/crypto/olmlib.js +++ b/src/crypto/olmlib.js @@ -35,6 +35,11 @@ module.exports.OLM_ALGORITHM = "m.olm.v1.curve25519-aes-sha2"; */ module.exports.MEGOLM_ALGORITHM = "m.megolm.v1.aes-sha2"; +/** + * matrix algorithm tag for megolm backups + */ +module.exports.MEGOLM_BACKUP_ALGORITHM = "m.megolm_backup.v1"; + /** * Encrypt an event payload for an Olm device From 75107f99b280899619b3de28f6ec1b5c3cdaaec9 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Thu, 23 Aug 2018 00:26:21 -0400 Subject: [PATCH 06/70] pass in key rather than decryption object to restoreKeyBackups --- spec/unit/crypto/backup.spec.js | 9 ++------- src/client.js | 8 ++++++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/spec/unit/crypto/backup.spec.js b/spec/unit/crypto/backup.spec.js index a4956647fe1..d13b5b6081e 100644 --- a/spec/unit/crypto/backup.spec.js +++ b/spec/unit/crypto/backup.spec.js @@ -215,14 +215,9 @@ describe("MegolmBackup", function() { } }); }; - const decryption = new global.Olm.PkDecryption(); - decryption.unpickle( - "secret_key", - "qx37WTQrjZLz5tId/uBX9B3/okqAbV1ofl9UnHKno1eipByCpXleAAlAZoJgYnCDOQZD" - + "QWzo3luTSfkF9pU1mOILCbbouubs6TVeDyPfgGD9i86J8irHjA" - ); return client.restoreKeyBackups( - decryption, + "qx37WTQrjZLz5tId/uBX9B3/okqAbV1ofl9UnHKno1eipByCpXleAAlAZoJgYnCDOQZD" + + "QWzo3luTSfkF9pU1mOILCbbouubs6TVeDyPfgGD9i86J8irHjA", ROOM_ID, 'o+21hSjP+mgEmcfdslPsQdvzWnkdt0Wyo00Kp++R8Kc' ).then(() => { diff --git a/src/client.js b/src/client.js index e9f651d49a4..56d546eb1c9 100644 --- a/src/client.js +++ b/src/client.js @@ -765,7 +765,7 @@ MatrixClient.prototype.createKeyBackupVersion = function(callback) { this._crypto.backupKey = encryption; // FIXME: pickle isn't the right thing to use, but we don't have // anything else yet - const recovery_key = decryption.pickle(""); + const recovery_key = decryption.pickle("secret_key"); callback(null, recovery_key); return recovery_key; }); @@ -818,6 +818,10 @@ MatrixClient.prototype.restoreKeyBackups = function(decryptionKey, roomId, sessi throw new Error("End-to-end encryption disabled"); } + // FIXME: see the FIXME in createKeyBackupVersion + const decryption = new global.Olm.PkDecryption(); + decryption.unpickle("secret_key", decryptionKey); + const path = this._makeKeyBackupPath(roomId, sessionId, version); return this._http.authedRequest( undefined, "GET", path.path, path.queryData, @@ -826,7 +830,7 @@ MatrixClient.prototype.restoreKeyBackups = function(decryptionKey, roomId, sessi // FIXME: for each room, session, if response has multiple // decrypt response.data.session_data const session_data = res.session_data; - const key = JSON.parse(decryptionKey.decrypt( + const key = JSON.parse(decryption.decrypt( session_data.ephemeral, session_data.mac, session_data.ciphertext From e5ec4799231b6618533c9e92d2f326920981bc42 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Thu, 23 Aug 2018 00:27:30 -0400 Subject: [PATCH 07/70] check that crypto is enabled --- src/client.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/client.js b/src/client.js index 56d546eb1c9..926b0935584 100644 --- a/src/client.js +++ b/src/client.js @@ -707,6 +707,10 @@ MatrixClient.prototype.importRoomKeys = function(keys) { * Get information about the current key backup. */ MatrixClient.prototype.getKeyBackupVersion = function(callback) { + if (this._crypto === null) { + throw new Error("End-to-end encryption disabled"); + } + return this._http.authedRequest( undefined, "GET", "/room_keys/version", ).then((res) => { @@ -733,6 +737,10 @@ MatrixClient.prototype.getKeyBackupVersion = function(callback) { * getKeyBackupVersion. */ MatrixClient.prototype.enableKeyBackup = function(info) { + if (this._crypto === null) { + throw new Error("End-to-end encryption disabled"); + } + this._crypto.backupKey = new global.Olm.PkEncryption(); this._crypto.backupKey.set_recipient_key(info.auth_data.public_key); } @@ -741,6 +749,10 @@ MatrixClient.prototype.enableKeyBackup = function(info) { * Disable backing up of keys. */ MatrixClient.prototype.disableKeyBackup = function() { + if (this._crypto === null) { + throw new Error("End-to-end encryption disabled"); + } + this._crypto.backupKey = undefined; } @@ -748,6 +760,10 @@ MatrixClient.prototype.disableKeyBackup = function() { * Create a new key backup version and enable it. */ MatrixClient.prototype.createKeyBackupVersion = function(callback) { + if (this._crypto === null) { + throw new Error("End-to-end encryption disabled"); + } + const decryption = new global.Olm.PkDecryption(); const public_key = decryption.generate_key(); const encryption = new global.Olm.PkEncryption(); From 73e294b1bd168e6e465406667987feba458618ef Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Thu, 23 Aug 2018 00:29:29 -0400 Subject: [PATCH 08/70] add copyright header to backup.spec --- spec/unit/crypto/backup.spec.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/spec/unit/crypto/backup.spec.js b/spec/unit/crypto/backup.spec.js index d13b5b6081e..f832f051147 100644 --- a/spec/unit/crypto/backup.spec.js +++ b/spec/unit/crypto/backup.spec.js @@ -1,3 +1,18 @@ +/* +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. +*/ try { global.Olm = require('olm'); } catch (e) { From 017f81e430cbb36f82ea78d6d1f4f7de24d5d6f9 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Fri, 24 Aug 2018 16:39:22 -0400 Subject: [PATCH 09/70] fix some bugs --- src/client.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/client.js b/src/client.js index 926b0935584..a434b67da0a 100644 --- a/src/client.js +++ b/src/client.js @@ -41,6 +41,7 @@ const SyncApi = require("./sync"); const MatrixBaseApis = require("./base-apis"); const MatrixError = httpApi.MatrixError; const ContentHelpers = require("./content-helpers"); +const olmlib = require("./crypto/olmlib"); import ReEmitter from './ReEmitter'; import RoomList from './crypto/RoomList'; @@ -782,7 +783,9 @@ MatrixClient.prototype.createKeyBackupVersion = function(callback) { // FIXME: pickle isn't the right thing to use, but we don't have // anything else yet const recovery_key = decryption.pickle("secret_key"); - callback(null, recovery_key); + if (callback) { + callback(null, recovery_key); + } return recovery_key; }); } From bf873bde42b1c59af496e9590b03109b1c51ac5e Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Fri, 24 Aug 2018 22:13:13 -0400 Subject: [PATCH 10/70] split the backup version creation into two different methods --- src/client.js | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/src/client.js b/src/client.js index a434b67da0a..3e0f8a5cc08 100644 --- a/src/client.js +++ b/src/client.js @@ -758,35 +758,49 @@ MatrixClient.prototype.disableKeyBackup = function() { } /** - * Create a new key backup version and enable it. + * Set up the data required to create a new backup version. The backup version + * will not be created and enabled until createKeyBackupVersion is called. */ -MatrixClient.prototype.createKeyBackupVersion = function(callback) { +MatrixClient.prototype.prepareKeyBackupVersion = function(callback) { if (this._crypto === null) { throw new Error("End-to-end encryption disabled"); } const decryption = new global.Olm.PkDecryption(); const public_key = decryption.generate_key(); - const encryption = new global.Olm.PkEncryption(); - encryption.set_recipient_key(public_key); - const data = { + return { algorithm: olmlib.MEGOLM_BACKUP_ALGORITHM, auth_data: { public_key: public_key, - } + }, + // FIXME: pickle isn't the right thing to use, but we don't have + // anything else yet, so use it for now + recovery_key: decryption.pickle("secret_key"), }; +} + +/** + * Create a new key backup version and enable it, using the information return + * from prepareKeyBackupVersion. + */ +MatrixClient.prototype.createKeyBackupVersion = function(info, callback) { + if (this._crypto === null) { + throw new Error("End-to-end encryption disabled"); + } + + const data = { + algorithm: info.algorithm, + auth_data: info.auth_data, // FIXME: should this be cloned? + } this._crypto._signObject(data.auth_data); return this._http.authedRequest( undefined, "POST", "/room_keys/version", undefined, data, ).then((res) => { - this._crypto.backupKey = encryption; - // FIXME: pickle isn't the right thing to use, but we don't have - // anything else yet - const recovery_key = decryption.pickle("secret_key"); + this.enableKeyBackup(info); if (callback) { - callback(null, recovery_key); + callback(null, res); } - return recovery_key; + return res; }); } From 3838fab7889cfcc897414710ef9b75be5c30f44e Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 13 Sep 2018 17:01:05 +0100 Subject: [PATCH 11/70] WIP e2e key backup support Continues from uhoreg's branch --- src/client.js | 68 +++++++++++++++++++++++++--- src/crypto/algorithms/megolm.js | 68 +++++++--------------------- src/crypto/index.js | 80 ++++++++++++++++++++++++++++++++- 3 files changed, 155 insertions(+), 61 deletions(-) diff --git a/src/client.js b/src/client.js index 1dbe4bfe937..78a43f16342 100644 --- a/src/client.js +++ b/src/client.js @@ -774,9 +774,27 @@ MatrixClient.prototype.getKeyBackupVersion = function(callback) { } return res; } + }).catch(e => { + if (e.errcode === 'M_NOT_FOUND') { + if (callback) callback(null); + return null; + } else { + throw e; + } }); } +/** + * @returns {bool} true if the client is configured to back up keys to + * the server, otherwise false. + */ +MatrixClient.prototype.getKeyBackupEnabled = function() { + if (this._crypto === null) { + throw new Error("End-to-end encryption disabled"); + } + return Boolean(this._crypto.backupKey); +} + /** * Enable backing up of keys, using data previously returned from * getKeyBackupVersion. @@ -786,6 +804,7 @@ MatrixClient.prototype.enableKeyBackup = function(info) { throw new Error("End-to-end encryption disabled"); } + this._crypto.backupInfo = info; this._crypto.backupKey = new global.Olm.PkEncryption(); this._crypto.backupKey.set_recipient_key(info.auth_data.public_key); } @@ -798,7 +817,8 @@ MatrixClient.prototype.disableKeyBackup = function() { throw new Error("End-to-end encryption disabled"); } - this._crypto.backupKey = undefined; + this._crypto.backupInfo = null; + this._crypto.backupKey = null; } /** @@ -836,11 +856,16 @@ MatrixClient.prototype.createKeyBackupVersion = function(info, callback) { algorithm: info.algorithm, auth_data: info.auth_data, // FIXME: should this be cloned? } - this._crypto._signObject(data.auth_data); - return this._http.authedRequest( - undefined, "POST", "/room_keys/version", undefined, data, - ).then((res) => { - this.enableKeyBackup(info); + return this._crypto._signObject(data.auth_data).then(() => { + return this._http.authedRequest( + undefined, "POST", "/room_keys/version", undefined, data, + ); + }).then((res) => { + this.enableKeyBackup({ + algorithm: info.algorithm, + auth_data: info.auth_data, + version: res.version, + }); if (callback) { callback(null, res); } @@ -848,6 +873,27 @@ MatrixClient.prototype.createKeyBackupVersion = function(info, callback) { }); } +MatrixClient.prototype.deleteKeyBackupVersion = function(version) { + if (this._crypto === null) { + throw new Error("End-to-end encryption disabled"); + } + + // If we're currently backing up to this backup... stop. + // (We start using it automatically in createKeyBackupVersion + // so this is symmetrical). + if (this._crypto.backupInfo && this._crypto.backupInfo.version === version) { + this.disableKeyBackup(); + } + + const path = utils.encodeUri("/room_keys/version/$version", { + $version: version, + }); + + return this._http.authedRequest( + undefined, "DELETE", path, undefined, undefined, + ); +}; + MatrixClient.prototype._makeKeyBackupPath = function(roomId, sessionId, version) { let path; if (sessionId !== undefined) { @@ -890,6 +936,14 @@ MatrixClient.prototype.sendKeyBackup = function(roomId, sessionId, version, data ); }; +MatrixClient.prototype.backupAllGroupSessions = function(version) { + if (this._crypto === null) { + throw new Error("End-to-end encryption disabled"); + } + + return this._crypto.backupAllGroupSessions(version); +}; + MatrixClient.prototype.restoreKeyBackups = function(decryptionKey, roomId, sessionId, version, callback) { if (this._crypto === null) { throw new Error("End-to-end encryption disabled"); @@ -924,7 +978,7 @@ MatrixClient.prototype.restoreKeyBackups = function(decryptionKey, roomId, sessi }) }; -MatrixClient.prototype.deleteKeyBackups = function(roomId, sessionId, version, callback) { +MatrixClient.prototype.deleteKeysFromBackup = function(roomId, sessionId, version, callback) { if (this._crypto === null) { throw new Error("End-to-end encryption disabled"); } diff --git a/src/crypto/algorithms/megolm.js b/src/crypto/algorithms/megolm.js index ac9c72f6857..af311e16bab 100644 --- a/src/crypto/algorithms/megolm.js +++ b/src/crypto/algorithms/megolm.js @@ -263,6 +263,14 @@ MegolmEncryption.prototype._prepareNewSession = async function() { key.key, {ed25519: this._olmDevice.deviceEd25519Key}, ); + if (this._crypto.backupInfo) { + // Not strictly necessary to wait for this + await this._crypto.backupGroupSession( + this._roomId, this._olmDevice.deviceCurve25519Key, [], + sessionId, key.key, + ); + } + return new OutboundSessionInfo(sessionId); }; @@ -840,11 +848,13 @@ MegolmDecryption.prototype.onRoomKeyEvent = function(event) { // have another go at decrypting events sent with this session. this._retryDecryption(senderKey, sessionId); }).then(() => { - return this.backupGroupSession( - content.room_id, senderKey, forwardingKeyChain, - content.session_id, content.session_key, keysClaimed, - exportFormat, - ); + if (this._crypto.backupInfo) { + return this._crypto.backupGroupSession( + content.room_id, senderKey, forwardingKeyChain, + content.session_id, content.session_key, keysClaimed, + exportFormat, + ); + } }).catch((e) => { console.error(`Error handling m.room_key_event: ${e}`); }); @@ -967,54 +977,6 @@ MegolmDecryption.prototype.importRoomKey = function(session) { }); }; -MegolmDecryption.prototype.backupGroupSession = async function( - roomId, senderKey, forwardingCurve25519KeyChain, - sessionId, sessionKey, keysClaimed, - exportFormat, -) { - // new session. - const session = new Olm.InboundGroupSession(); - let first_known_index; - try { - if (exportFormat) { - session.import_session(sessionKey); - } else { - session.create(sessionKey); - } - if (sessionId != session.session_id()) { - throw new Error( - "Mismatched group session ID from senderKey: " + - senderKey, - ); - } - - if (!exportFormat) { - sessionKey = session.export_session(); - } - const first_known_index = session.first_known_index(); - - const sessionData = { - algorithm: olmlib.MEGOLM_ALGORITHM, - sender_key: senderKey, - sender_claimed_keys: keysClaimed, - forwardingCurve25519KeyChain: forwardingCurve25519KeyChain, - session_key: sessionKey - }; - const encrypted = this._crypto.backupKey.encrypt(JSON.stringify(sessionData)); - const data = { - first_message_index: first_known_index, - forwarded_count: forwardingCurve25519KeyChain.length, - is_verified: false, // FIXME: how do we determine this? - session_data: encrypted - }; - return this._baseApis.sendKeyBackup(roomId, sessionId, data); - } catch (e) { - return Promise.reject(e); - } finally { - session.free(); - } -} - /** * Have another go at decrypting events after we receive a key * diff --git a/src/crypto/index.js b/src/crypto/index.js index 54bb0d738c6..fb8f8261445 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -75,7 +75,8 @@ function Crypto(baseApis, sessionStore, userId, deviceId, // track whether this device's megolm keys are being backed up incrementally // to the server or not. // XXX: this should probably have a single source of truth from OlmAccount - this.backupKey = null; + this.backupInfo = null; // The info dict from /room_keys/version + this.backupKey = null; // The encryption key object this._olmDevice = new OlmDevice(sessionStore, cryptoStore); this._deviceList = new DeviceList( @@ -848,6 +849,83 @@ Crypto.prototype.importRoomKeys = function(keys) { }, ); }; + +Crypto.prototype._backupPayloadForSession = function( + senderKey, forwardingCurve25519KeyChain, + sessionId, sessionKey, keysClaimed, + exportFormat, +) { + // new session. + const session = new Olm.InboundGroupSession(); + let first_known_index; + try { + if (exportFormat) { + session.import_session(sessionKey); + } else { + session.create(sessionKey); + } + if (sessionId != session.session_id()) { + throw new Error( + "Mismatched group session ID from senderKey: " + + senderKey, + ); + } + + if (!exportFormat) { + sessionKey = session.export_session(); + } + const first_known_index = session.first_known_index(); + + const sessionData = { + algorithm: olmlib.MEGOLM_ALGORITHM, + sender_key: senderKey, + sender_claimed_keys: keysClaimed, + session_key: sessionKey, + forwarding_curve25519_key_chain: forwardingCurve25519KeyChain, + }; + const encrypted = this.backupKey.encrypt(JSON.stringify(sessionData)); + return { + first_message_index: first_known_index, + forwarded_count: forwardingCurve25519KeyChain.length, + is_verified: false, // FIXME: how do we determine this? + session_data: encrypted, + }; + } finally { + session.free(); + } +}; + +Crypto.prototype.backupGroupSession = function( + roomId, senderKey, forwardingCurve25519KeyChain, + sessionId, sessionKey, keysClaimed, + exportFormat, +) { + if (!this.backupInfo) { + throw new Error("Key backups are not enabled"); + } + + const data = this._backupPayloadForSession( + senderKey, forwardingCurve25519KeyChain, + sessionId, sessionKey, keysClaimed, + exportFormat, + ); + return this._baseApis.sendKeyBackup(roomId, sessionId, this.backupInfo.version, data); +}; + +Crypto.prototype.backupAllGroupSessions = async function(version) { + const keys = await this.exportRoomKeys(); + const data = {}; + for (const key of keys) { + if (data[key.room_id] === undefined) data[key.room_id] = {sessions: {}}; + + data[key.room_id]['sessions'][key.session_id] = this._backupPayloadForSession( + key.sender_key, key.forwarding_curve25519_key_chain, + key.session_id, key.session_key, key.sender_claimed_keys, true, + ); + } + return this._baseApis.sendKeyBackup(undefined, undefined, version, {rooms: data}); +}; + /* eslint-disable valid-jsdoc */ //https://github.com/eslint/eslint/issues/7307 /** * Encrypt an event according to the configuration of the room. From e78974783416302578e0e90b2c64f3ef408c69c6 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 14 Sep 2018 17:06:27 +0100 Subject: [PATCH 12/70] Check sigs on e2e backup & enable it if we can --- src/client.js | 44 ++++++++++-- src/crypto/algorithms/megolm.js | 2 + src/crypto/index.js | 114 ++++++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+), 5 deletions(-) diff --git a/src/client.js b/src/client.js index 78a43f16342..7997b4c29f0 100644 --- a/src/client.js +++ b/src/client.js @@ -544,7 +544,15 @@ MatrixClient.prototype.setDeviceVerified = function(userId, deviceId, verified) if (verified === undefined) { verified = true; } - return _setDeviceVerification(this, userId, deviceId, verified, null); + const prom = _setDeviceVerification(this, userId, deviceId, verified, null); + + // if one of the user's own devices is being marked as verified / unverified, + // check the key backup status, since whether or not we use this depends on + // whether it has a signature from a verified device + if (userId == this.credentials.userId) { + this._crypto.checkKeyBackup(); + } + return prom; }; /** @@ -752,10 +760,6 @@ MatrixClient.prototype.importRoomKeys = function(keys) { * Get information about the current key backup. */ MatrixClient.prototype.getKeyBackupVersion = function(callback) { - if (this._crypto === null) { - throw new Error("End-to-end encryption disabled"); - } - return this._http.authedRequest( undefined, "GET", "/room_keys/version", ).then((res) => { @@ -784,6 +788,20 @@ MatrixClient.prototype.getKeyBackupVersion = function(callback) { }); } +/** + * @param {object} info key backup info dict from getKeyBackupVersion() + * @return {object} { + * usable: [bool], // is the backup trusted, true iff there is a sig that is valid & from a trusted device + * sigs: [ + * valid: [bool], + * device: [DeviceInfo], + * ] + * } + */ +MatrixClient.prototype.isKeyBackupTrusted = function(info) { + return this._crypto.isKeyBackupTrusted(info); +}; + /** * @returns {bool} true if the client is configured to back up keys to * the server, otherwise false. @@ -807,6 +825,8 @@ MatrixClient.prototype.enableKeyBackup = function(info) { this._crypto.backupInfo = info; this._crypto.backupKey = new global.Olm.PkEncryption(); this._crypto.backupKey.set_recipient_key(info.auth_data.public_key); + + this.emit('keyBackupStatus', true); } /** @@ -819,6 +839,8 @@ MatrixClient.prototype.disableKeyBackup = function() { this._crypto.backupInfo = null; this._crypto.backupKey = null; + + this.emit('keyBackupStatus', false); } /** @@ -3972,6 +3994,18 @@ module.exports.CRYPTO_ENABLED = CRYPTO_ENABLED; * }); */ +/** + * Fires whenever the status of e2e key backup changes, as returned by getKeyBackupEnabled() + * @event module:client~MatrixClient#"keyBackupStatus" + * @param {bool} enabled true if key backup has been enabled, otherwise false + * @example + * matrixClient.on("keyBackupStatus", function(enabled){ + * if (enabled) { + * [...] + * } + * }); + */ + /** * Fires when we want to suggest to the user that they restore their megolm keys * from backup or by cross-signing the device. diff --git a/src/crypto/algorithms/megolm.js b/src/crypto/algorithms/megolm.js index af311e16bab..f3cdbd17f3b 100644 --- a/src/crypto/algorithms/megolm.js +++ b/src/crypto/algorithms/megolm.js @@ -849,6 +849,8 @@ MegolmDecryption.prototype.onRoomKeyEvent = function(event) { this._retryDecryption(senderKey, sessionId); }).then(() => { if (this._crypto.backupInfo) { + // XXX: No retries on this at all: if this request dies for whatever + // reason, this key will never be uploaded. return this._crypto.backupGroupSession( content.room_id, senderKey, forwardingKeyChain, content.session_id, content.session_key, keysClaimed, diff --git a/src/crypto/index.js b/src/crypto/index.js index fb8f8261445..357410b2e85 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -77,6 +77,7 @@ function Crypto(baseApis, sessionStore, userId, deviceId, // XXX: this should probably have a single source of truth from OlmAccount this.backupInfo = null; // The info dict from /room_keys/version this.backupKey = null; // The encryption key object + this._checkedForBackup = false; // Have we checked the server for a backup we can use? this._olmDevice = new OlmDevice(sessionStore, cryptoStore); this._deviceList = new DeviceList( @@ -180,6 +181,113 @@ Crypto.prototype.init = async function() { ); this._deviceList.saveIfDirty(); } + + this._checkAndStartKeyBackup(); +}; + +/** + * Check the server for an active key backup and + * if one is present and has a valid signature from + * one of the user's verified devices, start backing up + * to it. + */ +Crypto.prototype._checkAndStartKeyBackup = async function() { + console.log("Checking key backup status..."); + let backupInfo; + try { + backupInfo = await this._baseApis.getKeyBackupVersion(); + } catch (e) { + console.log("Error checking for active key backup", e); + if (Number.isFinite(e.httpStatus) && e.httpStatus / 100 === 4) { + // well that's told us. we won't try again. + this._checkedForBackup = true; + } + return; + } + this._checkedForBackup = true; + + const trustInfo = await this.isKeyBackupTrusted(backupInfo); + + if (trustInfo.usable && !this.backupInfo) { + console.log("Found usable key backup: enabling key backups"); + this._baseApis.enableKeyBackup(backupInfo); + } else if (!trustInfo.usable && this.backupInfo) { + console.log("No usable key backup: disabling key backup"); + this._baseApis.disableKeyBackup(); + } else if (!trustInfo.usable && !this.backupInfo) { + console.log("No usable key backup: not enabling key backup"); + } +}; + +/** + * Forces a re-check of the key backup and enables/disables it + * as appropriate + */ +Crypto.prototype.checkKeyBackup = async function(backupInfo) { + this._checkedForBackup = false; + await this._checkAndStartKeyBackup(); +}; + +/** + * @param {object} backupInfo key backup info dict from /room_keys/version + * @return {object} { + * usable: [bool], // is the backup trusted, true iff there is a sig that is valid & from a trusted device + * sigs: [ + * valid: [bool], + * device: [DeviceInfo], + * ] + * } + */ +Crypto.prototype.isKeyBackupTrusted = async function(backupInfo) { + const ret = { + usable: false, + sigs: [], + }; + + if ( + !backupInfo || + !backupInfo.algorithm || + !backupInfo.auth_data || + !backupInfo.auth_data.public_key || + !backupInfo.auth_data.signatures + ) { + console.log("Key backup is absent or missing required data"); + return ret; + } + + const mySigs = backupInfo.auth_data.signatures[this._userId]; + if (!mySigs || mySigs.length === 0) { + console.log("Ignoring key backup because it lacks any signatures from this user"); + return ret; + } + + for (const keyId of Object.keys(mySigs)) { + const device = this._deviceList.getStoredDevice( + this._userId, keyId.split(':')[1], // XXX: is this how we're supposed to get the device ID? + ); + if (!device) { + console.log("Ignoring signature from unknown key " + keyId); + continue; + } + const sigInfo = { device }; + try { + await olmlib.verifySignature( + this._olmDevice, + backupInfo.auth_data, + this._userId, + device.deviceId, + device.getFingerprint(), + ); + sigInfo.valid = true; + } catch (e) { + console.log("Bad signature from device " + device.deviceId, e); + sigInfo.valid = false; + } + ret.sigs.push(sigInfo); + } + + ret.usable = ret.sigs.some(s => s.valid && s.device.isVerified()); + return ret; }; /** @@ -1233,6 +1341,12 @@ Crypto.prototype._onRoomKeyEvent = function(event) { return; } + if (!this._checkedForBackup) { + // don't bother awaiting on this - the important thing is that we retry if we + // haven't managed to check before + this._checkAndStartKeyBackup(); + } + const alg = this._getRoomDecryptor(content.room_id, content.algorithm); alg.onRoomKeyEvent(event); }; From 073fb73ff36aa7f56e810c8950ebe5b59fa26eb4 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 17 Sep 2018 15:59:37 +0100 Subject: [PATCH 13/70] Make multi-room key restore work --- src/client.js | 69 ++++++++++++++++++++++++--------- src/crypto/algorithms/megolm.js | 2 + 2 files changed, 53 insertions(+), 18 deletions(-) diff --git a/src/client.js b/src/client.js index 7997b4c29f0..9d6d33eb811 100644 --- a/src/client.js +++ b/src/client.js @@ -68,6 +68,31 @@ try { console.warn("Unable to load crypto module: crypto will be disabled: " + e); } +function keysFromRecoverySession(sessions, decryptionKey, roomId, keys) { + for (const [sessionId, sessionData] of Object.entries(sessions)) { + try { + const decrypted = keyFromRecoverySession(sessionData, decryptionKey, keys); + decrypted.session_id = sessionId; + decrypted.room_id = roomId; + return decrypted; + } catch (e) { + console.log("Failed to decrypt session from backup"); + } + } +} + +function keyFromRecoverySession(session, decryptionKey, keys) { + try { + keys.push(JSON.parse(decryptionKey.decrypt( + session.session_data.ephemeral, + session.session_data.mac, + session.session_data.ciphertext + ))); + } catch (e) { + console.log("Failed to decrypt key from backup", e); + } +} + /** * Construct a Matrix Client. Only directly construct this if you want to use * custom modules. Normally, {@link createClient} should be used @@ -966,7 +991,7 @@ MatrixClient.prototype.backupAllGroupSessions = function(version) { return this._crypto.backupAllGroupSessions(version); }; -MatrixClient.prototype.restoreKeyBackups = function(decryptionKey, roomId, sessionId, version, callback) { +MatrixClient.prototype.restoreKeyBackups = function(decryptionKey, targetRoomId, targetSessionId, version) { if (this._crypto === null) { throw new Error("End-to-end encryption disabled"); } @@ -975,28 +1000,36 @@ MatrixClient.prototype.restoreKeyBackups = function(decryptionKey, roomId, sessi const decryption = new global.Olm.PkDecryption(); decryption.unpickle("secret_key", decryptionKey); - const path = this._makeKeyBackupPath(roomId, sessionId, version); + let totalKeyCount = 0; + const keys = []; + + const path = this._makeKeyBackupPath(targetRoomId, targetSessionId, version); return this._http.authedRequest( undefined, "GET", path.path, path.queryData, ).then((res) => { - const keys = []; - // FIXME: for each room, session, if response has multiple - // decrypt response.data.session_data - const session_data = res.session_data; - const key = JSON.parse(decryption.decrypt( - session_data.ephemeral, - session_data.mac, - session_data.ciphertext - )); - // set room_id and session_id - key.room_id = roomId; - key.session_id = sessionId; - keys.push(key); + if (res.rooms) { + for (const [roomId, roomData] of Object.entries(res.rooms)) { + if (!roomData.sessions) continue; + + totalKeyCount += Object.keys(roomData.sessions).length; + const roomKeys = []; + keysFromRecoverySession(roomData.sessions, decryption, roomId, roomKeys); + for (const k of roomKeys) { + k.room_id = roomId; + keys.push(k); + } + } + } else if (res.sessions) { + totalKeyCount = Object.keys(res.sessions).length; + keys.push(...keysFromRecoverySession(res.sessions, decryption, roomId, keys)); + } else { + totalKeyCount = 1; + keys.push(keyFromRecoverySession(res, decryption, keys)); + } + return this.importRoomKeys(keys); }).then(() => { - if (callback) { - callback(); - } + return {total: totalKeyCount, imported: keys.length}; }) }; diff --git a/src/crypto/algorithms/megolm.js b/src/crypto/algorithms/megolm.js index f3cdbd17f3b..1e1de101ba8 100644 --- a/src/crypto/algorithms/megolm.js +++ b/src/crypto/algorithms/megolm.js @@ -851,6 +851,8 @@ MegolmDecryption.prototype.onRoomKeyEvent = function(event) { if (this._crypto.backupInfo) { // XXX: No retries on this at all: if this request dies for whatever // reason, this key will never be uploaded. + // More XXX: If this fails it'll cause the message send to fail, + // and this will happen if the backup is deleted from another client. return this._crypto.backupGroupSession( content.room_id, senderKey, forwardingKeyChain, content.session_id, content.session_key, keysClaimed, From 009430e829c3c5112c4b0d5179425c66f742ab03 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 17 Sep 2018 17:04:29 +0100 Subject: [PATCH 14/70] Add isValidRecoveryKey Add method to check if a given string is a valid recovery key --- src/client.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/client.js b/src/client.js index 9d6d33eb811..1c991c7da45 100644 --- a/src/client.js +++ b/src/client.js @@ -991,6 +991,24 @@ MatrixClient.prototype.backupAllGroupSessions = function(version) { return this._crypto.backupAllGroupSessions(version); }; +MatrixClient.prototype.isValidRecoveryKey = function(decryptionKey) { + if (this._crypto === null) { + throw new Error("End-to-end encryption disabled"); + } + + const decryption = new global.Olm.PkDecryption(); + try { + // FIXME: see the FIXME in createKeyBackupVersion + decryption.unpickle("secret_key", decryptionKey); + return true; + } catch (e) { + console.log(e); + return false; + } finally { + decryption.free(); + } +}; + MatrixClient.prototype.restoreKeyBackups = function(decryptionKey, targetRoomId, targetSessionId, version) { if (this._crypto === null) { throw new Error("End-to-end encryption disabled"); From f75d188131bb40d30ea2e5d401b6227fb367d437 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 17 Sep 2018 19:25:42 +0100 Subject: [PATCH 15/70] Soe progress on linting --- src/client.js | 80 +++++++++++++++++++++++++++------------------------ 1 file changed, 43 insertions(+), 37 deletions(-) diff --git a/src/client.js b/src/client.js index 1c991c7da45..c6d7e9f9e20 100644 --- a/src/client.js +++ b/src/client.js @@ -86,7 +86,7 @@ function keyFromRecoverySession(session, decryptionKey, keys) { keys.push(JSON.parse(decryptionKey.decrypt( session.session_data.ephemeral, session.session_data.mac, - session.session_data.ciphertext + session.session_data.ciphertext, ))); } catch (e) { console.log("Failed to decrypt key from backup", e); @@ -783,35 +783,30 @@ MatrixClient.prototype.importRoomKeys = function(keys) { /** * Get information about the current key backup. + * @returns {Promise} Information object from API or null */ -MatrixClient.prototype.getKeyBackupVersion = function(callback) { +MatrixClient.prototype.getKeyBackupVersion = function() { return this._http.authedRequest( undefined, "GET", "/room_keys/version", ).then((res) => { if (res.algorithm !== olmlib.MEGOLM_BACKUP_ALGORITHM) { const err = "Unknown backup algorithm: " + res.algorithm; - callback(err); return Promise.reject(err); } else if (!(typeof res.auth_data === "object") || !res.auth_data.public_key) { const err = "Invalid backup data returned"; - callback(err); return Promise.reject(err); } else { - if (callback) { - callback(null, res); - } return res; } - }).catch(e => { + }).catch((e) => { if (e.errcode === 'M_NOT_FOUND') { - if (callback) callback(null); return null; } else { throw e; } }); -} +}; /** * @param {object} info key backup info dict from getKeyBackupVersion() @@ -836,11 +831,13 @@ MatrixClient.prototype.getKeyBackupEnabled = function() { throw new Error("End-to-end encryption disabled"); } return Boolean(this._crypto.backupKey); -} +}; /** * Enable backing up of keys, using data previously returned from * getKeyBackupVersion. + * + * @param {object} info Backup information object as returned by getKeyBackupVersion */ MatrixClient.prototype.enableKeyBackup = function(info) { if (this._crypto === null) { @@ -852,7 +849,7 @@ MatrixClient.prototype.enableKeyBackup = function(info) { this._crypto.backupKey.set_recipient_key(info.auth_data.public_key); this.emit('keyBackupStatus', true); -} +}; /** * Disable backing up of keys. @@ -866,35 +863,41 @@ MatrixClient.prototype.disableKeyBackup = function() { this._crypto.backupKey = null; this.emit('keyBackupStatus', false); -} +}; /** * Set up the data required to create a new backup version. The backup version * will not be created and enabled until createKeyBackupVersion is called. + * + * @returns {object} Object that can be passed to createKeyBackupVersion and + * additionally has a 'recovery_key' member with the user-facing recovery key string. */ -MatrixClient.prototype.prepareKeyBackupVersion = function(callback) { +MatrixClient.prototype.prepareKeyBackupVersion = function() { if (this._crypto === null) { throw new Error("End-to-end encryption disabled"); } const decryption = new global.Olm.PkDecryption(); - const public_key = decryption.generate_key(); + const publicKey = decryption.generate_key(); return { algorithm: olmlib.MEGOLM_BACKUP_ALGORITHM, auth_data: { - public_key: public_key, + public_key: publicKey, }, // FIXME: pickle isn't the right thing to use, but we don't have // anything else yet, so use it for now recovery_key: decryption.pickle("secret_key"), }; -} +}; /** * Create a new key backup version and enable it, using the information return * from prepareKeyBackupVersion. + * + * @param {object} info Info object from prepareKeyBackupVersion + * @returns {Promise} Object with 'version' param indicating the version created */ -MatrixClient.prototype.createKeyBackupVersion = function(info, callback) { +MatrixClient.prototype.createKeyBackupVersion = function(info) { if (this._crypto === null) { throw new Error("End-to-end encryption disabled"); } @@ -902,7 +905,7 @@ MatrixClient.prototype.createKeyBackupVersion = function(info, callback) { const data = { algorithm: info.algorithm, auth_data: info.auth_data, // FIXME: should this be cloned? - } + }; return this._crypto._signObject(data.auth_data).then(() => { return this._http.authedRequest( undefined, "POST", "/room_keys/version", undefined, data, @@ -913,12 +916,9 @@ MatrixClient.prototype.createKeyBackupVersion = function(info, callback) { auth_data: info.auth_data, version: res.version, }); - if (callback) { - callback(null, res); - } return res; }); -} +}; MatrixClient.prototype.deleteKeyBackupVersion = function(version) { if (this._crypto === null) { @@ -955,31 +955,30 @@ MatrixClient.prototype._makeKeyBackupPath = function(roomId, sessionId, version) } else { path = "/room_keys/keys"; } - const queryData = version === undefined ? undefined : {version : version}; + const queryData = version === undefined ? undefined : { version: version }; return { path: path, queryData: queryData, - } -} + }; +}; /** * Back up session keys to the homeserver. * @param {string} roomId ID of the room that the keys are for Optional. * @param {string} sessionId ID of the session that the keys are for Optional. * @param {integer} version backup version Optional. - * @param {object} key data - * @param {module:client.callback} callback Optional. + * @param {object} data Object keys to send * @return {module:client.Promise} a promise that will resolve when the keys * are uploaded */ -MatrixClient.prototype.sendKeyBackup = function(roomId, sessionId, version, data, callback) { +MatrixClient.prototype.sendKeyBackup = function(roomId, sessionId, version, data) { if (this._crypto === null) { throw new Error("End-to-end encryption disabled"); } const path = this._makeKeyBackupPath(roomId, sessionId, version); return this._http.authedRequest( - callback, "PUT", path.path, path.queryData, data, + undefined, "PUT", path.path, path.queryData, data, ); }; @@ -1009,7 +1008,9 @@ MatrixClient.prototype.isValidRecoveryKey = function(decryptionKey) { } }; -MatrixClient.prototype.restoreKeyBackups = function(decryptionKey, targetRoomId, targetSessionId, version) { +MatrixClient.prototype.restoreKeyBackups = function( + decryptionKey, targetRoomId, targetSessionId, version, +) { if (this._crypto === null) { throw new Error("End-to-end encryption disabled"); } @@ -1039,27 +1040,32 @@ MatrixClient.prototype.restoreKeyBackups = function(decryptionKey, targetRoomId, } } else if (res.sessions) { totalKeyCount = Object.keys(res.sessions).length; - keys.push(...keysFromRecoverySession(res.sessions, decryption, roomId, keys)); + keys.push(...keysFromRecoverySession( + res.sessions, decryption, targetRoomId, keys, + )); } else { totalKeyCount = 1; - keys.push(keyFromRecoverySession(res, decryption, keys)); + const key = keyFromRecoverySession(res, decryption, keys); + key.room_id = targetRoomId; + key.session_id = targetSessionId; + keys.push(key); } return this.importRoomKeys(keys); }).then(() => { return {total: totalKeyCount, imported: keys.length}; - }) + }); }; -MatrixClient.prototype.deleteKeysFromBackup = function(roomId, sessionId, version, callback) { +MatrixClient.prototype.deleteKeysFromBackup = function(roomId, sessionId, version) { if (this._crypto === null) { throw new Error("End-to-end encryption disabled"); } const path = this._makeKeyBackupPath(roomId, sessionId, version); return this._http.authedRequest( - callback, "DELETE", path.path, path.queryData, - ) + undefined, "DELETE", path.path, path.queryData, + ); }; // Group ops From 3af9af96ea2ded3f75c4333f5c56f478fc56f815 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 17 Sep 2018 19:31:37 +0100 Subject: [PATCH 16/70] More linting --- src/crypto/index.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/crypto/index.js b/src/crypto/index.js index 357410b2e85..d45797fcb36 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -36,6 +36,11 @@ const DeviceList = require('./DeviceList').default; import OutgoingRoomKeyRequestManager from './OutgoingRoomKeyRequestManager'; import IndexedDBCryptoStore from './store/indexeddb-crypto-store'; +const Olm = global.Olm; +if (!Olm) { + throw new Error("global.Olm is not defined"); +} + /** * Cryptography bits * @@ -222,6 +227,8 @@ Crypto.prototype._checkAndStartKeyBackup = async function() { /** * Forces a re-check of the key backup and enables/disables it * as appropriate + * + * @param {object} backupInfo Backup info from /room_keys/version endpoint */ Crypto.prototype.checkKeyBackup = async function(backupInfo) { this._checkedForBackup = false; @@ -286,7 +293,7 @@ Crypto.prototype.isKeyBackupTrusted = async function(backupInfo) { ret.sigs.push(sigInfo); } - ret.usable = ret.sigs.some(s => s.valid && s.device.isVerified()); + ret.usable = ret.sigs.some((s) => s.valid && s.device.isVerified()); return ret; }; @@ -965,7 +972,6 @@ Crypto.prototype._backupPayloadForSession = function( ) { // new session. const session = new Olm.InboundGroupSession(); - let first_known_index; try { if (exportFormat) { session.import_session(sessionKey); @@ -982,7 +988,7 @@ Crypto.prototype._backupPayloadForSession = function( if (!exportFormat) { sessionKey = session.export_session(); } - const first_known_index = session.first_known_index(); + const firstKnownIndex = session.first_known_index(); const sessionData = { algorithm: olmlib.MEGOLM_ALGORITHM, @@ -993,7 +999,7 @@ Crypto.prototype._backupPayloadForSession = function( }; const encrypted = this.backupKey.encrypt(JSON.stringify(sessionData)); return { - first_message_index: first_known_index, + first_message_index: firstKnownIndex, forwarded_count: forwardingCurve25519KeyChain.length, is_verified: false, // FIXME: how do we determine this? session_data: encrypted, From 54c443ac68a2b386f52bd03e1df2fbd93aed1d73 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 18 Sep 2018 14:48:02 +0100 Subject: [PATCH 17/70] Make tests pass --- spec/unit/crypto/backup.spec.js | 5 ++++- src/client.js | 9 +++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/spec/unit/crypto/backup.spec.js b/spec/unit/crypto/backup.spec.js index f832f051147..8763d2d8b28 100644 --- a/spec/unit/crypto/backup.spec.js +++ b/spec/unit/crypto/backup.spec.js @@ -64,6 +64,9 @@ describe("MegolmBackup", function() { mockCrypto.backupKey.set_recipient_key( "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmoK" ); + mockCrypto.backupInfo = { + version: 1, + }; mockStorage = new MockStorageApi(); sessionStore = new WebStorageSessionStore(mockStorage); @@ -145,7 +148,7 @@ describe("MegolmBackup", function() { return event.attemptDecryption(mockCrypto).then(() => { return megolmDecryption.onRoomKeyEvent(event); }).then(() => { - expect(mockBaseApis.sendKeyBackup).toHaveBeenCalled(); + expect(mockCrypto.backupGroupSession).toHaveBeenCalled(); }); }); }); diff --git a/src/client.js b/src/client.js index c6d7e9f9e20..4838493e67f 100644 --- a/src/client.js +++ b/src/client.js @@ -1045,10 +1045,11 @@ MatrixClient.prototype.restoreKeyBackups = function( )); } else { totalKeyCount = 1; - const key = keyFromRecoverySession(res, decryption, keys); - key.room_id = targetRoomId; - key.session_id = targetSessionId; - keys.push(key); + keyFromRecoverySession(res, decryption, keys); + if (keys.length) { + keys[0].room_id = targetRoomId; + keys[0].session_id = targetSessionId; + } } return this.importRoomKeys(keys); From e4bb37b1a80090ef406016759ea30a90927985b1 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 18 Sep 2018 14:53:59 +0100 Subject: [PATCH 18/70] Fix lint mostly --- spec/unit/crypto/backup.spec.js | 40 +++++++++------------------------ 1 file changed, 11 insertions(+), 29 deletions(-) diff --git a/spec/unit/crypto/backup.spec.js b/spec/unit/crypto/backup.spec.js index 8763d2d8b28..ad8bc9c6d03 100644 --- a/spec/unit/crypto/backup.spec.js +++ b/spec/unit/crypto/backup.spec.js @@ -56,13 +56,13 @@ describe("MegolmBackup", function() { let sessionStore; let cryptoStore; let megolmDecryption; - beforeEach(function () { + beforeEach(function() { testUtils.beforeEach(this); // eslint-disable-line no-invalid-this mockCrypto = testUtils.mock(Crypto, 'Crypto'); mockCrypto.backupKey = new global.Olm.PkEncryption(); mockCrypto.backupKey.set_recipient_key( - "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmoK" + "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmoK", ); mockCrypto.backupInfo = { version: 1, @@ -125,24 +125,6 @@ describe("MegolmBackup", function() { return Promise.resolve(decryptedData); }; - const sessionId = groupSession.session_id(); - const cipherText = groupSession.encrypt(JSON.stringify({ - room_id: ROOM_ID, - content: 'testytest', - })); - const msgevent = new MatrixEvent({ - type: 'm.room.encrypted', - room_id: ROOM_ID, - content: { - algorithm: 'm.megolm.v1.aes-sha2', - sender_key: "SENDER_CURVE25519", - session_id: sessionId, - ciphertext: cipherText, - }, - event_id: "$event1", - origin_server_ts: 1507753886000, - }); - mockBaseApis.sendKeyBackup = expect.createSpy(); return event.attemptDecryption(mockCrypto).then(() => { @@ -153,7 +135,7 @@ describe("MegolmBackup", function() { }); }); - describe("restore", function () { + describe("restore", function() { let client; beforeEach(function() { @@ -163,9 +145,9 @@ describe("MegolmBackup", function() { ].reduce((r, k) => { r[k] = expect.createSpy(); return r; }, {}); const store = [ "getRoom", "getRooms", "getUser", "getSyncToken", "scrollback", - "save", "wantsSave", "setSyncToken", "storeEvents", "storeRoom", "storeUser", - "getFilterIdByName", "setFilterIdByName", "getFilter", "storeFilter", - "getSyncAccumulator", "startup", "deleteAllData", + "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.getSavedSyncToken = expect.createSpy().andReturn(Promise.resolve(null)); @@ -196,7 +178,7 @@ describe("MegolmBackup", function() { return client.initCrypto(); }); - it('can restore from backup', function () { + it('can restore from backup', function() { const event = new MatrixEvent({ type: 'm.room.encrypted', room_id: '!ROOM:ID', @@ -206,12 +188,12 @@ describe("MegolmBackup", function() { session_id: 'o+21hSjP+mgEmcfdslPsQdvzWnkdt0Wyo00Kp++R8Kc', ciphertext: 'AwgAEjD+VwXZ7PoGPRS/H4kwpAsMp/g+WPvJVtPEKE8fmM9IcT/N' + 'CiwPb8PehecDKP0cjm1XO88k6Bw3D17aGiBHr5iBoP7oSw8CXULXAMTkBl' - + 'mkufRQq2+d0Giy1s4/Cg5n13jSVrSb2q7VTSv1ZHAFjUCsLSfR0gxqcQs' + + 'mkufRQq2+d0Giy1s4/Cg5n13jSVrSb2q7VTSv1ZHAFjUCsLSfR0gxqcQs', }, event_id: '$event1', origin_server_ts: 1507753886000, }); - client._http.authedRequest = function () { + client._http.authedRequest = function() { return Promise.resolve({ first_message_index: 0, forwarded_count: 0, @@ -230,14 +212,14 @@ describe("MegolmBackup", function() { + 'iie8PHD8mj/5Y0GLqrac4CD6+Mop7eUTzVovprjg', mac: '5lxYBHQU80M', ephemeral: '/Bn0A4UMFwJaDDvh0aEk1XZj3k1IfgCxgFY9P9a0b14', - } + }, }); }; return client.restoreKeyBackups( "qx37WTQrjZLz5tId/uBX9B3/okqAbV1ofl9UnHKno1eipByCpXleAAlAZoJgYnCDOQZD" + "QWzo3luTSfkF9pU1mOILCbbouubs6TVeDyPfgGD9i86J8irHjA", ROOM_ID, - 'o+21hSjP+mgEmcfdslPsQdvzWnkdt0Wyo00Kp++R8Kc' + 'o+21hSjP+mgEmcfdslPsQdvzWnkdt0Wyo00Kp++R8Kc', ).then(() => { return megolmDecryption.decryptEvent(event); }).then((res) => { From 0bad7b213e705857b38e85912726b84abe7d4790 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 18 Sep 2018 14:56:11 +0100 Subject: [PATCH 19/70] 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. --- src/sync.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/sync.js b/src/sync.js index 2baf78215ab..bcb131c5910 100644 --- a/src/sync.js +++ b/src/sync.js @@ -1099,17 +1099,6 @@ SyncApi.prototype._processSyncResponse = async function( async function processRoomEvent(e) { client.emit("event", e); if (e.isState() && e.getType() == "m.room.encryption" && self.opts.crypto) { - - /* - // XXX: get device - if (!device.getSuggestedKeyRestore() && - !device.backupKey && !device.selfCrossSigs.length) - { - client.emit("crypto.suggestKeyRestore"); - device.setSuggestedKeyRestore(true); - } - */ - await self.opts.crypto.onCryptoEvent(e); } } From a78825eff9fa5b7d119bd93ce7a1408656263d55 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 18 Sep 2018 15:06:28 +0100 Subject: [PATCH 20/70] Bump to Olm 2.3.0 for PkEncryption --- travis.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/travis.sh b/travis.sh index 68d915defe4..4c47f00e719 100755 --- a/travis.sh +++ b/travis.sh @@ -5,7 +5,7 @@ set -ex npm run lint # install Olm so that we can run the crypto tests. -npm install https://matrix.org/packages/npm/olm/olm-2.2.2.tgz +npm install https://matrix.org/packages/npm/olm/olm-2.3.0.tgz npm run test From 1b62a21dbd37c9d4b46bddb4006e30368b8c0c2f Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 18 Sep 2018 16:12:37 +0100 Subject: [PATCH 21/70] Free PkEncryption/Decryption objects --- src/client.js | 43 ++++++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/src/client.js b/src/client.js index 4838493e67f..7a1a0748e2e 100644 --- a/src/client.js +++ b/src/client.js @@ -845,6 +845,7 @@ MatrixClient.prototype.enableKeyBackup = function(info) { } this._crypto.backupInfo = info; + if (this._crypto.backupKey) this._crypto.backupKey.free(); this._crypto.backupKey = new global.Olm.PkEncryption(); this._crypto.backupKey.set_recipient_key(info.auth_data.public_key); @@ -860,6 +861,7 @@ MatrixClient.prototype.disableKeyBackup = function() { } this._crypto.backupInfo = null; + if (this._crypto.backupKey) this._crypto.backupKey.free(); this._crypto.backupKey = null; this.emit('keyBackupStatus', false); @@ -878,16 +880,20 @@ MatrixClient.prototype.prepareKeyBackupVersion = function() { } const decryption = new global.Olm.PkDecryption(); - const publicKey = decryption.generate_key(); - return { - algorithm: olmlib.MEGOLM_BACKUP_ALGORITHM, - auth_data: { - public_key: publicKey, - }, - // FIXME: pickle isn't the right thing to use, but we don't have - // anything else yet, so use it for now - recovery_key: decryption.pickle("secret_key"), - }; + try { + const publicKey = decryption.generate_key(); + return { + algorithm: olmlib.MEGOLM_BACKUP_ALGORITHM, + auth_data: { + public_key: publicKey, + }, + // FIXME: pickle isn't the right thing to use, but we don't have + // anything else yet, so use it for now + recovery_key: decryption.pickle("secret_key"), + }; + } finally { + decryption.free(); + } }; /** @@ -1014,15 +1020,20 @@ MatrixClient.prototype.restoreKeyBackups = function( if (this._crypto === null) { throw new Error("End-to-end encryption disabled"); } - - // FIXME: see the FIXME in createKeyBackupVersion - const decryption = new global.Olm.PkDecryption(); - decryption.unpickle("secret_key", decryptionKey); - let totalKeyCount = 0; const keys = []; const path = this._makeKeyBackupPath(targetRoomId, targetSessionId, version); + + // FIXME: see the FIXME in createKeyBackupVersion + const decryption = new global.Olm.PkDecryption(); + try { + decryption.unpickle("secret_key", decryptionKey); + } catch(e) { + decryption.free(); + throw e; + } + return this._http.authedRequest( undefined, "GET", path.path, path.queryData, ).then((res) => { @@ -1055,6 +1066,8 @@ MatrixClient.prototype.restoreKeyBackups = function( return this.importRoomKeys(keys); }).then(() => { return {total: totalKeyCount, imported: keys.length}; + }).finally(() => { + decryption.free(); }); }; From 2f4c1dfcc4d3b5cd4ff5765e757cfdb6a9de026a Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 18 Sep 2018 17:33:47 +0100 Subject: [PATCH 22/70] Test all 3 code paths on backup restore --- spec/unit/crypto/backup.spec.js | 93 +++++++++++++++++++++------------ src/client.js | 44 ++++++++-------- 2 files changed, 81 insertions(+), 56 deletions(-) diff --git a/spec/unit/crypto/backup.spec.js b/spec/unit/crypto/backup.spec.js index ad8bc9c6d03..25f6c112a65 100644 --- a/spec/unit/crypto/backup.spec.js +++ b/spec/unit/crypto/backup.spec.js @@ -43,6 +43,42 @@ const MegolmDecryption = algorithms.DECRYPTION_CLASSES['m.megolm.v1.aes-sha2']; const ROOM_ID = '!ROOM:ID'; +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: 'o+21hSjP+mgEmcfdslPsQdvzWnkdt0Wyo00Kp++R8Kc', + 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', + }, +}; + describe("MegolmBackup", function() { if (!global.Olm) { console.warn('Not running megolm backup unit tests: libolm not present'); @@ -179,49 +215,38 @@ describe("MegolmBackup", function() { }); it('can restore from backup', function() { - const event = new MatrixEvent({ - type: 'm.room.encrypted', - room_id: '!ROOM:ID', - content: { - algorithm: 'm.megolm.v1.aes-sha2', - sender_key: 'SENDER_CURVE25519', - session_id: 'o+21hSjP+mgEmcfdslPsQdvzWnkdt0Wyo00Kp++R8Kc', - ciphertext: 'AwgAEjD+VwXZ7PoGPRS/H4kwpAsMp/g+WPvJVtPEKE8fmM9IcT/N' - + 'CiwPb8PehecDKP0cjm1XO88k6Bw3D17aGiBHr5iBoP7oSw8CXULXAMTkBl' - + 'mkufRQq2+d0Giy1s4/Cg5n13jSVrSb2q7VTSv1ZHAFjUCsLSfR0gxqcQs', - }, - event_id: '$event1', - origin_server_ts: 1507753886000, + client._http.authedRequest = function() { + return Promise.resolve(KEY_BACKUP_DATA); + }; + return client.restoreKeyBackups( + "qx37WTQrjZLz5tId/uBX9B3/okqAbV1ofl9UnHKno1eipByCpXleAAlAZoJgYnCDOQZD" + + "QWzo3luTSfkF9pU1mOILCbbouubs6TVeDyPfgGD9i86J8irHjA", + ROOM_ID, + 'o+21hSjP+mgEmcfdslPsQdvzWnkdt0Wyo00Kp++R8Kc', + ).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({ - 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', - }, + rooms: { + [ROOM_ID]: { + sessions: { + 'o+21hSjP+mgEmcfdslPsQdvzWnkdt0Wyo00Kp++R8Kc': KEY_BACKUP_DATA, + }, + }, + } }); }; return client.restoreKeyBackups( "qx37WTQrjZLz5tId/uBX9B3/okqAbV1ofl9UnHKno1eipByCpXleAAlAZoJgYnCDOQZD" + "QWzo3luTSfkF9pU1mOILCbbouubs6TVeDyPfgGD9i86J8irHjA", - ROOM_ID, - 'o+21hSjP+mgEmcfdslPsQdvzWnkdt0Wyo00Kp++R8Kc', ).then(() => { - return megolmDecryption.decryptEvent(event); + return megolmDecryption.decryptEvent(ENCRYPTED_EVENT); }).then((res) => { expect(res.clearEvent.content).toEqual('testytest'); }); diff --git a/src/client.js b/src/client.js index 7a1a0748e2e..c864bf49000 100644 --- a/src/client.js +++ b/src/client.js @@ -68,29 +68,27 @@ try { console.warn("Unable to load crypto module: crypto will be disabled: " + e); } -function keysFromRecoverySession(sessions, decryptionKey, roomId, keys) { +function keysFromRecoverySession(sessions, decryptionKey, roomId) { + const keys = []; for (const [sessionId, sessionData] of Object.entries(sessions)) { try { - const decrypted = keyFromRecoverySession(sessionData, decryptionKey, keys); + const decrypted = keyFromRecoverySession(sessionData, decryptionKey); decrypted.session_id = sessionId; decrypted.room_id = roomId; - return decrypted; + keys.push(decrypted); } catch (e) { console.log("Failed to decrypt session from backup"); } } + return keys; } -function keyFromRecoverySession(session, decryptionKey, keys) { - try { - keys.push(JSON.parse(decryptionKey.decrypt( - session.session_data.ephemeral, - session.session_data.mac, - session.session_data.ciphertext, - ))); - } catch (e) { - console.log("Failed to decrypt key from backup", e); - } +function keyFromRecoverySession(session, decryptionKey) { + return JSON.parse(decryptionKey.decrypt( + session.session_data.ephemeral, + session.session_data.mac, + session.session_data.ciphertext, + )); } /** @@ -1021,7 +1019,7 @@ MatrixClient.prototype.restoreKeyBackups = function( throw new Error("End-to-end encryption disabled"); } let totalKeyCount = 0; - const keys = []; + let keys = []; const path = this._makeKeyBackupPath(targetRoomId, targetSessionId, version); @@ -1042,8 +1040,7 @@ MatrixClient.prototype.restoreKeyBackups = function( if (!roomData.sessions) continue; totalKeyCount += Object.keys(roomData.sessions).length; - const roomKeys = []; - keysFromRecoverySession(roomData.sessions, decryption, roomId, roomKeys); + const roomKeys = keysFromRecoverySession(roomData.sessions, decryption, roomId, roomKeys); for (const k of roomKeys) { k.room_id = roomId; keys.push(k); @@ -1051,15 +1048,18 @@ MatrixClient.prototype.restoreKeyBackups = function( } } else if (res.sessions) { totalKeyCount = Object.keys(res.sessions).length; - keys.push(...keysFromRecoverySession( + keys = keysFromRecoverySession( res.sessions, decryption, targetRoomId, keys, - )); + ); } else { totalKeyCount = 1; - keyFromRecoverySession(res, decryption, keys); - if (keys.length) { - keys[0].room_id = targetRoomId; - keys[0].session_id = targetSessionId; + try { + const key = keyFromRecoverySession(res, decryption); + key.room_id = targetRoomId; + key.session_id = targetSessionId; + keys.push(key); + } catch (e) { + console.log("Failed to decrypt session from backup"); } } From 7cd101d8cb06a036b5c67737c47e01249955f621 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 2 Oct 2018 19:22:10 +0100 Subject: [PATCH 23/70] Fix recovery key format --- package.json | 1 + src/client.js | 23 +++++++---------------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index db0b14bb179..4ae0b36a747 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "dependencies": { "another-json": "^0.2.0", "babel-runtime": "^6.26.0", + "base58check": "^2.0.0", "bluebird": "^3.5.0", "browser-request": "^0.3.3", "content-type": "^1.0.2", diff --git a/src/client.js b/src/client.js index ceeeff42670..951dbcdbbbd 100644 --- a/src/client.js +++ b/src/client.js @@ -49,6 +49,7 @@ import {InvalidStoreError} from './errors'; import Crypto from './crypto'; import { isCryptoAvailable } from './crypto'; +import { encodeRecoveryKey, decodeRecoveryKey } from './crypto/recoverykey'; const LAZY_LOADING_MESSAGES_FILTER = { lazy_load_members: true, @@ -882,9 +883,7 @@ MatrixClient.prototype.prepareKeyBackupVersion = function() { auth_data: { public_key: publicKey, }, - // FIXME: pickle isn't the right thing to use, but we don't have - // anything else yet, so use it for now - recovery_key: decryption.pickle("secret_key"), + recovery_key: encodeRecoveryKey(decryption.get_private_key()), }; } finally { decryption.free(); @@ -991,26 +990,17 @@ MatrixClient.prototype.backupAllGroupSessions = function(version) { return this._crypto.backupAllGroupSessions(version); }; -MatrixClient.prototype.isValidRecoveryKey = function(decryptionKey) { - if (this._crypto === null) { - throw new Error("End-to-end encryption disabled"); - } - - const decryption = new global.Olm.PkDecryption(); +MatrixClient.prototype.isValidRecoveryKey = function(recoveryKey) { try { - // FIXME: see the FIXME in createKeyBackupVersion - decryption.unpickle("secret_key", decryptionKey); + decodeRecoveryKey(recoveryKey); return true; } catch (e) { - console.log(e); return false; - } finally { - decryption.free(); } }; MatrixClient.prototype.restoreKeyBackups = function( - decryptionKey, targetRoomId, targetSessionId, version, + recoveryKey, targetRoomId, targetSessionId, version, ) { if (this._crypto === null) { throw new Error("End-to-end encryption disabled"); @@ -1021,9 +1011,10 @@ MatrixClient.prototype.restoreKeyBackups = function( const path = this._makeKeyBackupPath(targetRoomId, targetSessionId, version); // FIXME: see the FIXME in createKeyBackupVersion + const privkey = decodeRecoveryKey(recoveryKey); const decryption = new global.Olm.PkDecryption(); try { - decryption.unpickle("secret_key", decryptionKey); + decryption.init_with_private_key(privkey); } catch(e) { decryption.free(); throw e; From 262ace1773f22bb917078e74fb72c6e1dabe38da Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 3 Oct 2018 10:20:57 +0100 Subject: [PATCH 24/70] commit the recovery key util file --- src/crypto/recoverykey.js | 44 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/crypto/recoverykey.js diff --git a/src/crypto/recoverykey.js b/src/crypto/recoverykey.js new file mode 100644 index 00000000000..82de3a15823 --- /dev/null +++ b/src/crypto/recoverykey.js @@ -0,0 +1,44 @@ +/* +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 base58check from 'base58check'; + +// picked arbitrarily but to try & avoid clashing with any bitcoin ones +const OLM_RECOVERY_KEY_PREFIX = [0x8B, 0x01]; + +export function encodeRecoveryKey(key) { + const base58key = base58check.encode(Buffer.from(OLM_RECOVERY_KEY_PREFIX), Buffer.from(key)); + return base58key.match(/.{1,4}/g).join(" "); +} + +export function decodeRecoveryKey(recoverykey) { + const result = base58check.decode(recoverykey.replace(/ /, '')); + // the encoding doesn't include the length of the prefix, so the + // decoder assumes it's 1 byte. sigh. + const prefix = Buffer.concat([result.prefix, result.data.slice(0, OLM_RECOVERY_KEY_PREFIX.length - 1)]); + + if (!prefix.equals(Buffer.from(OLM_RECOVERY_KEY_PREFIX))) { + throw new Error("Incorrect prefix"); + } + + const key = result.data.slice(OLM_RECOVERY_KEY_PREFIX.length - 1); + + if (key.length !== global.Olm.PRIVATE_KEY_LENGTH) { + throw new Error("Incorrect length"); + } + + return key; +} From 258adda67c0b7d43a053249f564260e10de8908d Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Thu, 4 Oct 2018 15:19:20 -0400 Subject: [PATCH 25/70] retry key backups when they fail --- spec/unit/crypto/backup.spec.js | 192 +++++++++++++++++- src/client.js | 2 + src/crypto/index.js | 128 ++++++------ .../store/indexeddb-crypto-store-backend.js | 70 +++++++ src/crypto/store/indexeddb-crypto-store.js | 33 +++ src/crypto/store/memory-crypto-store.js | 35 ++++ 6 files changed, 400 insertions(+), 60 deletions(-) diff --git a/spec/unit/crypto/backup.spec.js b/spec/unit/crypto/backup.spec.js index 25f6c112a65..7a7f0b4147b 100644 --- a/spec/unit/crypto/backup.spec.js +++ b/spec/unit/crypto/backup.spec.js @@ -98,7 +98,7 @@ describe("MegolmBackup", function() { mockCrypto = testUtils.mock(Crypto, 'Crypto'); mockCrypto.backupKey = new global.Olm.PkEncryption(); mockCrypto.backupKey.set_recipient_key( - "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmoK", + "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo", ); mockCrypto.backupInfo = { version: 1, @@ -134,7 +134,7 @@ describe("MegolmBackup", function() { megolmDecryption.olmlib = mockOlmLib; }); - it('automatically backs up keys', function() { + it('automatically calls the key back up', function() { const groupSession = new global.Olm.OutboundGroupSession(); groupSession.create(); @@ -169,6 +169,194 @@ describe("MegolmBackup", function() { expect(mockCrypto.backupGroupSession).toHaveBeenCalled(); }); }); + + it('sends backups to the server', function () { + const groupSession = new global.Olm.OutboundGroupSession(); + groupSession.create(); + const ibGroupSession = new global.Olm.InboundGroupSession(); + ibGroupSession.create(groupSession.session_key()); + + const scheduler = [ + "getQueueForEvent", "queueEvent", "removeEventFromQueue", + "setProcessFunction", + ].reduce((r, k) => { r[k] = expect.createSpy(); 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] = expect.createSpy(); return r; }, {}); + store.getSavedSync = expect.createSpy().andReturn(Promise.resolve(null)); + store.getSavedSyncToken = expect.createSpy().andReturn(Promise.resolve(null)); + store.setSyncData = expect.createSpy().andReturn(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/Og0mOBr066SpjqqbTmoK" + }, + }); + let numCalls = 0; + return new Promise((resolve, reject) => { + client._http.authedRequest = function(callback, method, path, queryParams, data, opts) { + expect(++numCalls <= 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).toExist(); + expect(data.rooms[ROOM_ID].sessions).toIncludeKey(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('retries when a backup fails', function () { + const groupSession = new global.Olm.OutboundGroupSession(); + groupSession.create(); + const ibGroupSession = new global.Olm.InboundGroupSession(); + ibGroupSession.create(groupSession.session_key()); + + const scheduler = [ + "getQueueForEvent", "queueEvent", "removeEventFromQueue", + "setProcessFunction", + ].reduce((r, k) => { r[k] = expect.createSpy(); 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] = expect.createSpy(); return r; }, {}); + store.getSavedSync = expect.createSpy().andReturn(Promise.resolve(null)); + store.getSavedSyncToken = expect.createSpy().andReturn(Promise.resolve(null)); + store.setSyncData = expect.createSpy().andReturn(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/Og0mOBr066SpjqqbTmoK" + }, + }); + let numCalls = 0; + return new Promise((resolve, reject) => { + client._http.authedRequest = function(callback, method, path, queryParams, data, opts) { + expect(++numCalls <= 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).toExist(); + expect(data.rooms[ROOM_ID].sessions).toIncludeKey(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() { diff --git a/src/client.js b/src/client.js index c864bf49000..7080fbcf6ad 100644 --- a/src/client.js +++ b/src/client.js @@ -848,6 +848,8 @@ MatrixClient.prototype.enableKeyBackup = function(info) { this._crypto.backupKey.set_recipient_key(info.auth_data.public_key); this.emit('keyBackupStatus', true); + + this._crypto._maybeSendKeyBackup(); }; /** diff --git a/src/crypto/index.js b/src/crypto/index.js index d45797fcb36..41dfdff731c 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -83,6 +83,7 @@ function Crypto(baseApis, sessionStore, userId, deviceId, this.backupInfo = null; // The info dict from /room_keys/version this.backupKey = null; // The encryption key object this._checkedForBackup = false; // Have we checked the server for a backup we can use? + this._sendingBackups = false; // Are we currently sending backups? this._olmDevice = new OlmDevice(sessionStore, cryptoStore); this._deviceList = new DeviceList( @@ -965,51 +966,62 @@ Crypto.prototype.importRoomKeys = function(keys) { ); }; -Crypto.prototype._backupPayloadForSession = function( - senderKey, forwardingCurve25519KeyChain, - sessionId, sessionKey, keysClaimed, - exportFormat, -) { - // new session. - const session = new Olm.InboundGroupSession(); - try { - if (exportFormat) { - session.import_session(sessionKey); - } else { - session.create(sessionKey); - } - if (sessionId != session.session_id()) { - throw new Error( - "Mismatched group session ID from senderKey: " + - senderKey, - ); - } +Crypto.prototype._maybeSendKeyBackup = async function() { + if (!this._sendingBackups) { + this._sendingBackups = true; + while (1) { + if (!this.backupKey) { + this._sendingBackups = false; + return; + } + // FIXME: figure out what limit is reasonable + const sessions = await this._cryptoStore.getSessionsNeedingBackup(10); + if (!sessions.length) { + this._sendingBackups = false; + return; + } + const data = {}; + for (const session of sessions) { + const room_id = session.sessionData.room_id; + if (data[room_id] === undefined) + data[room_id] = {sessions: {}}; + + const sessionData = await this._olmDevice.exportInboundGroupSession(session.senderKey, session.sessionId, session.sessionData); + sessionData.algorithm = olmlib.MEGOLM_ALGORITHM; + delete sessionData.session_id; + delete sessionData.room_id; + const encrypted = this.backupKey.encrypt(JSON.stringify(sessionData)); + + data[room_id]['sessions'][session.sessionId] = { + first_message_index: 1, // FIXME + forwarded_count: (sessionData.forwardingCurve25519KeyChain || []).length, + is_verified: false, // FIXME: how do we determine this? + session_data: encrypted, + }; + } - if (!exportFormat) { - sessionKey = session.export_session(); + let successful = false; + do { + if (!this.backupKey) { + this._sendingBackups = false; + return; + } + try { + await this._baseApis.sendKeyBackup(undefined, undefined, this.backupInfo.version, {rooms: data}); + successful = true; + await this._cryptoStore.unmarkSessionsNeedingBackup(sessions); + } + catch (e) { + console.log("send failed", e); + // FIXME: pause + } + } while (!successful); + // FIXME: pause between iterations? } - const firstKnownIndex = session.first_known_index(); - - const sessionData = { - algorithm: olmlib.MEGOLM_ALGORITHM, - sender_key: senderKey, - sender_claimed_keys: keysClaimed, - session_key: sessionKey, - forwarding_curve25519_key_chain: forwardingCurve25519KeyChain, - }; - const encrypted = this.backupKey.encrypt(JSON.stringify(sessionData)); - return { - first_message_index: firstKnownIndex, - forwarded_count: forwardingCurve25519KeyChain.length, - is_verified: false, // FIXME: how do we determine this? - session_data: encrypted, - }; - } finally { - session.free(); } -}; +} -Crypto.prototype.backupGroupSession = function( +Crypto.prototype.backupGroupSession = async function( roomId, senderKey, forwardingCurve25519KeyChain, sessionId, sessionKey, keysClaimed, exportFormat, @@ -1018,26 +1030,26 @@ Crypto.prototype.backupGroupSession = function( throw new Error("Key backups are not enabled"); } - const data = this._backupPayloadForSession( - senderKey, forwardingCurve25519KeyChain, - sessionId, sessionKey, keysClaimed, - exportFormat, - ); - return this._baseApis.sendKeyBackup(roomId, sessionId, this.backupInfo.version, data); + await this._cryptoStore.markSessionsNeedingBackup([{ + senderKey: senderKey, + sessionId: sessionId, + }]); + + this._maybeSendKeyBackup(); }; Crypto.prototype.backupAllGroupSessions = async function(version) { - const keys = await this.exportRoomKeys(); - const data = {}; - for (const key of keys) { - if (data[key.room_id] === undefined) data[key.room_id] = {sessions: {}}; - - data[key.room_id]['sessions'][key.session_id] = this._backupPayloadForSession( - key.sender_key, key.forwarding_curve25519_key_chain, - key.session_id, key.session_key, key.sender_claimed_keys, true, - ); - } - return this._baseApis.sendKeyBackup(undefined, undefined, version, {rooms: data}); + await this._cryptoStore.doTxn( + 'readwrite', [IndexedDBCryptoStore.STORE_SESSIONS, IndexedDBCryptoStore.STORE_BACKUP], (txn) => { + this._cryptoStore.getAllEndToEndInboundGroupSessions(txn, (session) => { + if (session !== null) { + this._cryptoStore.markSessionsNeedingBackup([session], txn); + } + }); + } + ); + + this._maybeSendKeyBackup(); }; /* eslint-disable valid-jsdoc */ //https://github.com/eslint/eslint/issues/7307 diff --git a/src/crypto/store/indexeddb-crypto-store-backend.js b/src/crypto/store/indexeddb-crypto-store-backend.js index 4a7f4878936..9935dbd38be 100644 --- a/src/crypto/store/indexeddb-crypto-store-backend.js +++ b/src/crypto/store/indexeddb-crypto-store-backend.js @@ -460,6 +460,71 @@ export class Backend { }; } + // 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 + }); + } + //sessions.push(cursor.value); + if (!limit || sessions.length < limit) { + cursor.continue(); + } + } + } + }); + } + + unmarkSessionsNeedingBackup(sessions) { + const 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) => { + console.log(session); + 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); @@ -498,6 +563,11 @@ export function upgradeDatabase(db, oldVersion) { if (oldVersion < 6) { db.createObjectStore("rooms"); } + if (oldVersion < 7) { + db.createObjectStore("sessions_needing_backup", { + keyPath: ["senderCurve25519Key", "sessionId"], + }); + } // Expand as needed. } diff --git a/src/crypto/store/indexeddb-crypto-store.js b/src/crypto/store/indexeddb-crypto-store.js index 249b29b63cd..0eca4c373e0 100644 --- a/src/crypto/store/indexeddb-crypto-store.js +++ b/src/crypto/store/indexeddb-crypto-store.js @@ -407,6 +407,38 @@ export default class IndexedDBCryptoStore { this._backendPromise.value().getEndToEndRooms(txn, func); } + /** + * Get the inbound group sessions that need to be backed up. + * @param {integer} limit The maximum number of sessions to retrieve. 0 + * for no limit. + */ + getSessionsNeedingBackup(limit) { + return this._connect().then((backend) => { + return backend.getSessionsNeedingBackup(limit); + }); + } + + /** + * Unmark sessions as needing to be backed up. + * @param {[object]} The sessions that need to be backed up. + */ + unmarkSessionsNeedingBackup(sessions) { + return this._connect().then((backend) => { + return backend.unmarkSessionsNeedingBackup(sessions); + }); + } + + /** + * Mark sessions as needing to be backed up. + * @param {[object]} The sessions that need to be backed up. + * @param {*} txn An active transaction. See doTxn(). (optional) + */ + markSessionsNeedingBackup(sessions, txn) { + return this._connect().then((backend) => { + return backend.markSessionsNeedingBackup(sessions, txn); + }); + } + /** * Perform a transaction on the crypto store. Any store methods * that require a transaction (txn) object to be passed in may @@ -440,3 +472,4 @@ IndexedDBCryptoStore.STORE_SESSIONS = 'sessions'; IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS = 'inbound_group_sessions'; IndexedDBCryptoStore.STORE_DEVICE_DATA = 'device_data'; IndexedDBCryptoStore.STORE_ROOMS = 'rooms'; +IndexedDBCryptoStore.STORE_BACKUP = 'sessions_needing_backup'; diff --git a/src/crypto/store/memory-crypto-store.js b/src/crypto/store/memory-crypto-store.js index 469cdb49bc7..cd5ec5be4a9 100644 --- a/src/crypto/store/memory-crypto-store.js +++ b/src/crypto/store/memory-crypto-store.js @@ -41,6 +41,8 @@ export default class MemoryCryptoStore { this._deviceData = null; // roomId -> Opaque roomInfo object this._rooms = {}; + // Set of {senderCurve25519Key+'/'+sessionId} + this._sessionsNeedingBackup = {}; } /** @@ -295,6 +297,39 @@ export default class MemoryCryptoStore { func(this._rooms); } + getSessionsNeedingBackup(limit) { + const sessions = []; + for (const session in this._sessionsNeedingBackup) { + if (this._inboundGroupSessions[session]) { + sessions.push({ + senderKey: session.substr(0, 43), + sessionId: session.substr(44), + sessionData: this._inboundGroupSessions[session], + }); + if (limit && session.length >= limit) { + break; + } + } + } + return Promise.resolve(sessions); + } + + unmarkSessionsNeedingBackup(sessions) { + for(const session of sessions) { + delete this._sessionsNeedingBackup[session.senderKey + '/' + session.sessionId]; + } + return Promise.resolve(); + } + + markSessionsNeedingBackup(sessions) { + for(const session of sessions) { + this._sessionsNeedingBackup[session.senderKey + '/' + session.sessionId] = true; + } + return Promise.resolve(); + } + + // Session key backups + doTxn(mode, stores, func) { return Promise.resolve(func(null)); } From 59e60665795ecbd3684d1ada12ced5c9341c7f11 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 9 Oct 2018 14:15:03 +0100 Subject: [PATCH 26/70] 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. --- package.json | 2 +- src/crypto/recoverykey.js | 42 +++++++++++++++++++++++++++++---------- 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 4ae0b36a747..17c3ec5ce00 100644 --- a/package.json +++ b/package.json @@ -53,9 +53,9 @@ "dependencies": { "another-json": "^0.2.0", "babel-runtime": "^6.26.0", - "base58check": "^2.0.0", "bluebird": "^3.5.0", "browser-request": "^0.3.3", + "bs58": "^4.0.1", "content-type": "^1.0.2", "request": "^2.53.0" }, diff --git a/src/crypto/recoverykey.js b/src/crypto/recoverykey.js index 82de3a15823..69dc8ae6e0e 100644 --- a/src/crypto/recoverykey.js +++ b/src/crypto/recoverykey.js @@ -14,31 +14,51 @@ See the License for the specific language governing permissions and limitations under the License. */ -import base58check from 'base58check'; +import bs58 from 'bs58'; // picked arbitrarily but to try & avoid clashing with any bitcoin ones +// (also base58 encoded, albeit with a lot of hashing) const OLM_RECOVERY_KEY_PREFIX = [0x8B, 0x01]; export function encodeRecoveryKey(key) { - const base58key = base58check.encode(Buffer.from(OLM_RECOVERY_KEY_PREFIX), Buffer.from(key)); + const buf = new Uint8Array(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 = base58check.decode(recoverykey.replace(/ /, '')); - // the encoding doesn't include the length of the prefix, so the - // decoder assumes it's 1 byte. sigh. - const prefix = Buffer.concat([result.prefix, result.data.slice(0, OLM_RECOVERY_KEY_PREFIX.length - 1)]); + const result = bs58.decode(recoverykey.replace(/ /g, '')); - if (!prefix.equals(Buffer.from(OLM_RECOVERY_KEY_PREFIX))) { - throw new Error("Incorrect prefix"); + let parity = 0; + for (const b of result) { + parity ^= b; + } + if (parity !== 0) { + throw new Error("Incorrect parity"); } - const key = result.data.slice(OLM_RECOVERY_KEY_PREFIX.length - 1); + 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 (key.length !== global.Olm.PRIVATE_KEY_LENGTH) { + if (result.length !== OLM_RECOVERY_KEY_PREFIX.length + global.Olm.PRIVATE_KEY_LENGTH + 1) { throw new Error("Incorrect length"); } - return key; + return result.slice( + OLM_RECOVERY_KEY_PREFIX.length, + OLM_RECOVERY_KEY_PREFIX.length + global.Olm.PRIVATE_KEY_LENGTH, + ); } From ada4b6ef16a3557e24b54e494d5b5a6f644c64f3 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 9 Oct 2018 15:46:12 +0100 Subject: [PATCH 27/70] Lint --- spec/unit/crypto/backup.spec.js | 9 +++++---- src/client.js | 4 +++- src/crypto/index.js | 2 +- src/crypto/recoverykey.js | 5 ++++- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/spec/unit/crypto/backup.spec.js b/spec/unit/crypto/backup.spec.js index 25f6c112a65..f1da584f425 100644 --- a/spec/unit/crypto/backup.spec.js +++ b/spec/unit/crypto/backup.spec.js @@ -43,13 +43,14 @@ 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: 'o+21hSjP+mgEmcfdslPsQdvzWnkdt0Wyo00Kp++R8Kc', + session_id: SESSION_ID, ciphertext: 'AwgAEjD+VwXZ7PoGPRS/H4kwpAsMp/g+WPvJVtPEKE8fmM9IcT/N' + 'CiwPb8PehecDKP0cjm1XO88k6Bw3D17aGiBHr5iBoP7oSw8CXULXAMTkBl' + 'mkufRQq2+d0Giy1s4/Cg5n13jSVrSb2q7VTSv1ZHAFjUCsLSfR0gxqcQs', @@ -222,7 +223,7 @@ describe("MegolmBackup", function() { "qx37WTQrjZLz5tId/uBX9B3/okqAbV1ofl9UnHKno1eipByCpXleAAlAZoJgYnCDOQZD" + "QWzo3luTSfkF9pU1mOILCbbouubs6TVeDyPfgGD9i86J8irHjA", ROOM_ID, - 'o+21hSjP+mgEmcfdslPsQdvzWnkdt0Wyo00Kp++R8Kc', + SESSION_ID, ).then(() => { return megolmDecryption.decryptEvent(ENCRYPTED_EVENT); }).then((res) => { @@ -236,10 +237,10 @@ describe("MegolmBackup", function() { rooms: { [ROOM_ID]: { sessions: { - 'o+21hSjP+mgEmcfdslPsQdvzWnkdt0Wyo00Kp++R8Kc': KEY_BACKUP_DATA, + SESSION_ID: KEY_BACKUP_DATA, }, }, - } + }, }); }; return client.restoreKeyBackups( diff --git a/src/client.js b/src/client.js index 5a9481122ed..9c3a86911a6 100644 --- a/src/client.js +++ b/src/client.js @@ -1032,7 +1032,9 @@ MatrixClient.prototype.restoreKeyBackups = function( if (!roomData.sessions) continue; totalKeyCount += Object.keys(roomData.sessions).length; - const roomKeys = keysFromRecoverySession(roomData.sessions, decryption, roomId, roomKeys); + const roomKeys = keysFromRecoverySession( + roomData.sessions, decryption, roomId, roomKeys, + ); for (const k of roomKeys) { k.room_id = roomId; keys.push(k); diff --git a/src/crypto/index.js b/src/crypto/index.js index 7aa76e37ead..b6926b837bb 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -974,7 +974,7 @@ Crypto.prototype._backupPayloadForSession = function( exportFormat, ) { // new session. - const session = new Olm.InboundGroupSession(); + const session = new global.Olm.InboundGroupSession(); try { if (exportFormat) { session.import_session(sessionKey); diff --git a/src/crypto/recoverykey.js b/src/crypto/recoverykey.js index 69dc8ae6e0e..bb85697e86e 100644 --- a/src/crypto/recoverykey.js +++ b/src/crypto/recoverykey.js @@ -53,7 +53,10 @@ export function decodeRecoveryKey(recoverykey) { } } - if (result.length !== OLM_RECOVERY_KEY_PREFIX.length + global.Olm.PRIVATE_KEY_LENGTH + 1) { + if ( + result.length !== + OLM_RECOVERY_KEY_PREFIX.length + global.Olm.PRIVATE_KEY_LENGTH + 1 + ) { throw new Error("Incorrect length"); } From da65f43983af1e5504ef0b1ba2d4a38c6d9acc81 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Wed, 10 Oct 2018 19:31:28 -0400 Subject: [PATCH 28/70] wrap backup sending in a try, and add delays --- spec/unit/crypto/backup.spec.js | 2 + src/crypto/index.js | 94 +++++++++++++++++++-------------- 2 files changed, 56 insertions(+), 40 deletions(-) diff --git a/spec/unit/crypto/backup.spec.js b/spec/unit/crypto/backup.spec.js index 7a7f0b4147b..de910592bff 100644 --- a/spec/unit/crypto/backup.spec.js +++ b/spec/unit/crypto/backup.spec.js @@ -171,6 +171,7 @@ describe("MegolmBackup", function() { }); it('sends backups to the server', function () { + this.timeout(12000); const groupSession = new global.Olm.OutboundGroupSession(); groupSession.create(); const ibGroupSession = new global.Olm.InboundGroupSession(); @@ -263,6 +264,7 @@ describe("MegolmBackup", function() { }); it('retries when a backup fails', function () { + this.timeout(12000); const groupSession = new global.Olm.OutboundGroupSession(); groupSession.create(); const ibGroupSession = new global.Olm.InboundGroupSession(); diff --git a/src/crypto/index.js b/src/crypto/index.js index 41dfdff731c..7104320ee33 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -969,54 +969,68 @@ Crypto.prototype.importRoomKeys = function(keys) { Crypto.prototype._maybeSendKeyBackup = async function() { if (!this._sendingBackups) { this._sendingBackups = true; - while (1) { - if (!this.backupKey) { - this._sendingBackups = false; - return; - } - // FIXME: figure out what limit is reasonable - const sessions = await this._cryptoStore.getSessionsNeedingBackup(10); - if (!sessions.length) { - this._sendingBackups = false; - return; - } - const data = {}; - for (const session of sessions) { - const room_id = session.sessionData.room_id; - if (data[room_id] === undefined) - data[room_id] = {sessions: {}}; - - const sessionData = await this._olmDevice.exportInboundGroupSession(session.senderKey, session.sessionId, session.sessionData); - sessionData.algorithm = olmlib.MEGOLM_ALGORITHM; - delete sessionData.session_id; - delete sessionData.room_id; - const encrypted = this.backupKey.encrypt(JSON.stringify(sessionData)); - - data[room_id]['sessions'][session.sessionId] = { - first_message_index: 1, // FIXME - forwarded_count: (sessionData.forwardingCurve25519KeyChain || []).length, - is_verified: false, // FIXME: how do we determine this? - session_data: encrypted, - }; - } - - let successful = false; - do { + try { + // wait between 0 and 10 seconds, to avoid backup requests from + // different clients hitting the server all at the same time when a + // new key is sent + await new Promise((resolve, reject) => { + setTimeout(resolve, Math.random() * 10000); + }); + let numFailures = 0; // number of consecutive failures + while (1) { if (!this.backupKey) { - this._sendingBackups = false; return; } + // FIXME: figure out what limit is reasonable + const sessions = await this._cryptoStore.getSessionsNeedingBackup(10); + if (!sessions.length) { + return; + } + const data = {}; + for (const session of sessions) { + const room_id = session.sessionData.room_id; + if (data[room_id] === undefined) + data[room_id] = {sessions: {}}; + + const sessionData = await this._olmDevice.exportInboundGroupSession(session.senderKey, session.sessionId, session.sessionData); + sessionData.algorithm = olmlib.MEGOLM_ALGORITHM; + delete sessionData.session_id; + delete sessionData.room_id; + const encrypted = this.backupKey.encrypt(JSON.stringify(sessionData)); + + data[room_id]['sessions'][session.sessionId] = { + first_message_index: 1, // FIXME + forwarded_count: (sessionData.forwardingCurve25519KeyChain || []).length, + is_verified: false, // FIXME: how do we determine this? + session_data: encrypted, + }; + } + try { await this._baseApis.sendKeyBackup(undefined, undefined, this.backupInfo.version, {rooms: data}); - successful = true; + numFailures = 0; await this._cryptoStore.unmarkSessionsNeedingBackup(sessions); } - catch (e) { - console.log("send failed", e); - // FIXME: pause + catch (err) { + numFailures++; + console.log("send failed", err); + if (err.httpStatus === 400 || err.httpStatus === 403 || err.httpStatus === 401) { + // retrying probably won't help much, so we should give up + // FIXME: disable backups? + return; + } } - } while (!successful); - // FIXME: pause between iterations? + if (numFailures) { + // exponential backoff if we have failures + await new Promise((resolve, reject) => { + setTimeout(resolve, 1000 * Math.pow(2, Math.min(numFailures - 1, 4))); + }); + } + } + } + finally + { + this._sendingBackups = false; } } } From fc59bc2992d9543be987aaac60a22aa567aa81eb Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Wed, 10 Oct 2018 19:32:07 -0400 Subject: [PATCH 29/70] add localstorage support for key backups --- src/crypto/store/localStorage-crypto-store.js | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/crypto/store/localStorage-crypto-store.js b/src/crypto/store/localStorage-crypto-store.js index 3f2f0d09a5b..71a904fd805 100644 --- a/src/crypto/store/localStorage-crypto-store.js +++ b/src/crypto/store/localStorage-crypto-store.js @@ -32,6 +32,7 @@ const KEY_END_TO_END_ACCOUNT = E2E_PREFIX + "account"; const KEY_DEVICE_DATA = E2E_PREFIX + "device_data"; const KEY_INBOUND_SESSION_PREFIX = E2E_PREFIX + "inboundgroupsessions/"; const KEY_ROOMS_PREFIX = E2E_PREFIX + "rooms/"; +const KEY_SESSIONS_NEEDING_BACKUP = E2E_PREFIX + "sessionsneedingbackup"; function keyEndToEndSessions(deviceKey) { return E2E_PREFIX + "sessions/" + deviceKey; @@ -165,6 +166,48 @@ export default class LocalStorageCryptoStore extends MemoryCryptoStore { func(result); } + getSessionsNeedingBackup(limit) { + const sessions = []; + + for (const session in getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP)) { + const senderKey = session.substr(0, 43); + const sessionId = session.substr(44); + getEndToEndInboundGroupSession(senderKey, sessionId, null, (sessionData) => { + sessions.push({ + senderKey: senderKey, + sessionId: sessionId, + sessionData: sessionData, + }); + }) + if (limit && session.length >= limit) { + break; + } + } + return Promise.resolve(sessions); + } + + unmarkSessionsNeedingBackup(sessions) { + const sessionsNeedingBackup = getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {}; + for(const session of sessions) { + delete sessionsNeedingBackup[session.senderKey + '/' + session.sessionId]; + } + setJsonItem( + this.store, KEY_SESSION_NEEDING_BACKUP, sessionsNeedinBackup, + ); + return Promise.resolve(); + } + + markSessionsNeedingBackup(sessions) { + const sessionsNeedingBackup = getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {}; + for(const session of sessions) { + sessionsNeedingBackup[session.senderKey + '/' + session.sessionId] = true; + } + setJsonItem( + this.store, KEY_SESSION_NEEDING_BACKUP, sessionsNeedinBackup, + ); + return Promise.resolve(); + } + /** * Delete all data from this store. * From 9b12c228235e5f13dfe9664f00f827e094b49adf Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Fri, 12 Oct 2018 10:38:10 -0400 Subject: [PATCH 30/70] de-lint plus some minor fixes --- .eslintrc.js | 1 + spec/unit/crypto/backup.spec.js | 148 +++++++++++------- src/crypto/algorithms/megolm.js | 4 - src/crypto/index.js | 45 ++++-- .../store/indexeddb-crypto-store-backend.js | 19 +-- src/crypto/store/indexeddb-crypto-store.js | 9 +- 6 files changed, 138 insertions(+), 88 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index fec2d7b5ada..ae1826de529 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -16,6 +16,7 @@ module.exports = { }, extends: ["eslint:recommended", "google"], rules: { + "indent": ["error", 4], // rules we've always adhered to or now do "max-len": ["error", { code: 90, diff --git a/spec/unit/crypto/backup.spec.js b/spec/unit/crypto/backup.spec.js index b1948c8a25f..7217f32266e 100644 --- a/spec/unit/crypto/backup.spec.js +++ b/spec/unit/crypto/backup.spec.js @@ -37,6 +37,8 @@ if (global.Olm) { Crypto = require('../../../lib/crypto'); } +const Olm = global.Olm; + const MatrixClient = sdk.MatrixClient; const MatrixEvent = sdk.MatrixEvent; const MegolmDecryption = algorithms.DECRYPTION_CLASSES['m.megolm.v1.aes-sha2']; @@ -93,16 +95,21 @@ describe("MegolmBackup", function() { let sessionStore; let cryptoStore; let megolmDecryption; - beforeEach(function() { + beforeEach(async function() { + await Olm.init(); testUtils.beforeEach(this); // eslint-disable-line no-invalid-this mockCrypto = testUtils.mock(Crypto, 'Crypto'); - mockCrypto.backupKey = new global.Olm.PkEncryption(); + mockCrypto.backupKey = new Olm.PkEncryption(); mockCrypto.backupKey.set_recipient_key( "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo", ); mockCrypto.backupInfo = { + algorithm: "m.megolm_backup.v1", version: 1, + auth_data: { + public_key: "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo", + }, }; mockStorage = new MockStorageApi(); @@ -136,7 +143,7 @@ describe("MegolmBackup", function() { }); it('automatically calls the key back up', function() { - const groupSession = new global.Olm.OutboundGroupSession(); + const groupSession = new Olm.OutboundGroupSession(); groupSession.create(); // construct a fake decrypted key event via the use of a mocked @@ -161,8 +168,9 @@ describe("MegolmBackup", function() { mockCrypto.decryptEvent = function() { return Promise.resolve(decryptedData); }; + mockCrypto.cancelRoomKeyRequest = function() {}; - mockBaseApis.sendKeyBackup = expect.createSpy(); + mockCrypto.backupGroupSession = expect.createSpy(); return event.attemptDecryption(mockCrypto).then(() => { return megolmDecryption.onRoomKeyEvent(event); @@ -171,23 +179,23 @@ describe("MegolmBackup", function() { }); }); - it('sends backups to the server', function () { - this.timeout(12000); - const groupSession = new global.Olm.OutboundGroupSession(); + it('sends backups to the server', function() { + this.timeout(12000); // eslint-disable-line no-invalid-this + const groupSession = new Olm.OutboundGroupSession(); groupSession.create(); - const ibGroupSession = new global.Olm.InboundGroupSession(); + const ibGroupSession = new Olm.InboundGroupSession(); ibGroupSession.create(groupSession.session_key()); const scheduler = [ "getQueueForEvent", "queueEvent", "removeEventFromQueue", "setProcessFunction", - ].reduce((r, k) => { r[k] = expect.createSpy(); return r; }, {}); + ].reduce((r, k) => {r[k] = expect.createSpy(); 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] = expect.createSpy(); return r; }, {}); + ].reduce((r, k) => {r[k] = expect.createSpy(); return r;}, {}); store.getSavedSync = expect.createSpy().andReturn(Promise.resolve(null)); store.getSavedSyncToken = expect.createSpy().andReturn(Promise.resolve(null)); store.setSyncData = expect.createSpy().andReturn(Promise.resolve(null)); @@ -216,32 +224,37 @@ describe("MegolmBackup", function() { 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", + 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), }, - room_id: ROOM_ID, - session: ibGroupSession.pickle(olmDevice._pickleKey), - }, - txn); - }); + txn); + }); }) .then(() => { client.enableKeyBackup({ - algorithm: "foobar", + algorithm: "m.megolm_backup.v1", version: 1, auth_data: { - public_key: "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmoK" + public_key: "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo", }, }); let numCalls = 0; return new Promise((resolve, reject) => { - client._http.authedRequest = function(callback, method, path, queryParams, data, opts) { + client._http.authedRequest = function( + callback, method, path, queryParams, data, opts, + ) { expect(++numCalls <= 1); if (numCalls >= 2) { // exit out of retry loop if there's something wrong @@ -252,11 +265,19 @@ describe("MegolmBackup", function() { expect(path).toBe("/room_keys/keys"); expect(queryParams.version).toBe(1); expect(data.rooms[ROOM_ID].sessions).toExist(); - expect(data.rooms[ROOM_ID].sessions).toIncludeKey(groupSession.session_id()); + expect(data.rooms[ROOM_ID].sessions).toIncludeKey( + groupSession.session_id(), + ); resolve(); return Promise.resolve({}); }; - client._crypto.backupGroupSession("roomId", "F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI", [], groupSession.session_id(), groupSession.session_key()); + client._crypto.backupGroupSession( + "roomId", + "F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI", + [], + groupSession.session_id(), + groupSession.session_key(), + ); }) .then(() => { expect(numCalls).toBe(1); @@ -264,23 +285,23 @@ describe("MegolmBackup", function() { }); }); - it('retries when a backup fails', function () { - this.timeout(12000); - const groupSession = new global.Olm.OutboundGroupSession(); + it('retries when a backup fails', function() { + this.timeout(12000); // eslint-disable-line no-invalid-this + const groupSession = new Olm.OutboundGroupSession(); groupSession.create(); - const ibGroupSession = new global.Olm.InboundGroupSession(); + const ibGroupSession = new Olm.InboundGroupSession(); ibGroupSession.create(groupSession.session_key()); const scheduler = [ "getQueueForEvent", "queueEvent", "removeEventFromQueue", "setProcessFunction", - ].reduce((r, k) => { r[k] = expect.createSpy(); return r; }, {}); + ].reduce((r, k) => {r[k] = expect.createSpy(); 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] = expect.createSpy(); return r; }, {}); + ].reduce((r, k) => {r[k] = expect.createSpy(); return r;}, {}); store.getSavedSync = expect.createSpy().andReturn(Promise.resolve(null)); store.getSavedSyncToken = expect.createSpy().andReturn(Promise.resolve(null)); store.setSyncData = expect.createSpy().andReturn(Promise.resolve(null)); @@ -309,32 +330,37 @@ describe("MegolmBackup", function() { 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", + 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), }, - room_id: ROOM_ID, - session: ibGroupSession.pickle(olmDevice._pickleKey), - }, - txn); - }); + txn); + }); }) .then(() => { client.enableKeyBackup({ algorithm: "foobar", version: 1, auth_data: { - public_key: "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmoK" + public_key: "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo", }, }); let numCalls = 0; return new Promise((resolve, reject) => { - client._http.authedRequest = function(callback, method, path, queryParams, data, opts) { + client._http.authedRequest = function( + callback, method, path, queryParams, data, opts, + ) { expect(++numCalls <= 2); if (numCalls >= 3) { // exit out of retry loop if there's something wrong @@ -345,15 +371,25 @@ describe("MegolmBackup", function() { expect(path).toBe("/room_keys/keys"); expect(queryParams.version).toBe(1); expect(data.rooms[ROOM_ID].sessions).toExist(); - expect(data.rooms[ROOM_ID].sessions).toIncludeKey(groupSession.session_id()); + expect(data.rooms[ROOM_ID].sessions).toIncludeKey( + groupSession.session_id(), + ); if (numCalls > 1) { resolve(); return Promise.resolve({}); } else { - return Promise.reject(new Error("this is an expected failure")); + return Promise.reject( + new Error("this is an expected failure"), + ); } }; - client._crypto.backupGroupSession("roomId", "F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI", [], groupSession.session_id(), groupSession.session_key()); + client._crypto.backupGroupSession( + "roomId", + "F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI", + [], + groupSession.session_id(), + groupSession.session_key(), + ); }) .then(() => { expect(numCalls).toBe(2); @@ -369,13 +405,13 @@ describe("MegolmBackup", function() { const scheduler = [ "getQueueForEvent", "queueEvent", "removeEventFromQueue", "setProcessFunction", - ].reduce((r, k) => { r[k] = expect.createSpy(); return r; }, {}); + ].reduce((r, k) => {r[k] = expect.createSpy(); 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] = expect.createSpy(); return r; }, {}); + ].reduce((r, k) => {r[k] = expect.createSpy(); return r;}, {}); store.getSavedSync = expect.createSpy().andReturn(Promise.resolve(null)); store.getSavedSyncToken = expect.createSpy().andReturn(Promise.resolve(null)); store.setSyncData = expect.createSpy().andReturn(Promise.resolve(null)); @@ -411,7 +447,7 @@ describe("MegolmBackup", function() { }; return client.restoreKeyBackups( "qx37WTQrjZLz5tId/uBX9B3/okqAbV1ofl9UnHKno1eipByCpXleAAlAZoJgYnCDOQZD" - + "QWzo3luTSfkF9pU1mOILCbbouubs6TVeDyPfgGD9i86J8irHjA", + + "QWzo3luTSfkF9pU1mOILCbbouubs6TVeDyPfgGD9i86J8irHjA", ROOM_ID, SESSION_ID, ).then(() => { @@ -435,7 +471,7 @@ describe("MegolmBackup", function() { }; return client.restoreKeyBackups( "qx37WTQrjZLz5tId/uBX9B3/okqAbV1ofl9UnHKno1eipByCpXleAAlAZoJgYnCDOQZD" - + "QWzo3luTSfkF9pU1mOILCbbouubs6TVeDyPfgGD9i86J8irHjA", + + "QWzo3luTSfkF9pU1mOILCbbouubs6TVeDyPfgGD9i86J8irHjA", ).then(() => { return megolmDecryption.decryptEvent(ENCRYPTED_EVENT); }).then((res) => { diff --git a/src/crypto/algorithms/megolm.js b/src/crypto/algorithms/megolm.js index 1e1de101ba8..af311e16bab 100644 --- a/src/crypto/algorithms/megolm.js +++ b/src/crypto/algorithms/megolm.js @@ -849,10 +849,6 @@ MegolmDecryption.prototype.onRoomKeyEvent = function(event) { this._retryDecryption(senderKey, sessionId); }).then(() => { if (this._crypto.backupInfo) { - // XXX: No retries on this at all: if this request dies for whatever - // reason, this key will never be uploaded. - // More XXX: If this fails it'll cause the message send to fail, - // and this will happen if the backup is deleted from another client. return this._crypto.backupGroupSession( content.room_id, senderKey, forwardingKeyChain, content.session_id, content.session_key, keysClaimed, diff --git a/src/crypto/index.js b/src/crypto/index.js index 0ca0d8506d5..d45caabe396 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -991,33 +991,41 @@ Crypto.prototype._maybeSendKeyBackup = async function() { } const data = {}; for (const session of sessions) { - const room_id = session.sessionData.room_id; - if (data[room_id] === undefined) - data[room_id] = {sessions: {}}; + const roomId = session.sessionData.room_id; + if (data[roomId] === undefined) { + data[roomId] = {sessions: {}}; + } - const sessionData = await this._olmDevice.exportInboundGroupSession(session.senderKey, session.sessionId, session.sessionData); + const sessionData = await this._olmDevice.exportInboundGroupSession( + session.senderKey, session.sessionId, session.sessionData, + ); sessionData.algorithm = olmlib.MEGOLM_ALGORITHM; delete sessionData.session_id; delete sessionData.room_id; const encrypted = this.backupKey.encrypt(JSON.stringify(sessionData)); - data[room_id]['sessions'][session.sessionId] = { + data[roomId]['sessions'][session.sessionId] = { first_message_index: 1, // FIXME - forwarded_count: (sessionData.forwardingCurve25519KeyChain || []).length, + forwarded_count: + (sessionData.forwardingCurve25519KeyChain || []).length, is_verified: false, // FIXME: how do we determine this? session_data: encrypted, }; } try { - await this._baseApis.sendKeyBackup(undefined, undefined, this.backupInfo.version, {rooms: data}); + await this._baseApis.sendKeyBackup( + undefined, undefined, this.backupInfo.version, + {rooms: data}, + ); numFailures = 0; await this._cryptoStore.unmarkSessionsNeedingBackup(sessions); - } - catch (err) { + } catch (err) { numFailures++; console.log("send failed", err); - if (err.httpStatus === 400 || err.httpStatus === 403 || err.httpStatus === 401) { + if (err.httpStatus === 400 + || err.httpStatus === 403 + || err.httpStatus === 401) { // retrying probably won't help much, so we should give up // FIXME: disable backups? return; @@ -1026,17 +1034,18 @@ Crypto.prototype._maybeSendKeyBackup = async function() { if (numFailures) { // exponential backoff if we have failures await new Promise((resolve, reject) => { - setTimeout(resolve, 1000 * Math.pow(2, Math.min(numFailures - 1, 4))); + setTimeout( + resolve, + 1000 * Math.pow(2, Math.min(numFailures - 1, 4)), + ); }); } } - } - finally - { + } finally { this._sendingBackups = false; } } -} +}; Crypto.prototype.backupGroupSession = async function( roomId, senderKey, forwardingCurve25519KeyChain, @@ -1057,13 +1066,15 @@ Crypto.prototype.backupGroupSession = async function( Crypto.prototype.backupAllGroupSessions = async function(version) { await this._cryptoStore.doTxn( - 'readwrite', [IndexedDBCryptoStore.STORE_SESSIONS, IndexedDBCryptoStore.STORE_BACKUP], (txn) => { + 'readwrite', + [IndexedDBCryptoStore.STORE_SESSIONS, IndexedDBCryptoStore.STORE_BACKUP], + (txn) => { this._cryptoStore.getAllEndToEndInboundGroupSessions(txn, (session) => { if (session !== null) { this._cryptoStore.markSessionsNeedingBackup([session], txn); } }); - } + }, ); this._maybeSendKeyBackup(); diff --git a/src/crypto/store/indexeddb-crypto-store-backend.js b/src/crypto/store/indexeddb-crypto-store-backend.js index 9935dbd38be..d0bb9f1b7d8 100644 --- a/src/crypto/store/indexeddb-crypto-store-backend.js +++ b/src/crypto/store/indexeddb-crypto-store-backend.js @@ -466,31 +466,33 @@ export class Backend { return new Promise((resolve, reject) => { const sessions = []; - const txn = this._db.transaction(["sessions_needing_backup", "inbound_group_sessions"], "readonly"); + 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) + const sessionGetReq = sessionStore.get(cursor.key); sessionGetReq.onsuccess = function() { sessions.push({ senderKey: sessionGetReq.result.senderCurve25519Key, sessionId: sessionGetReq.result.sessionId, - sessionData: sessionGetReq.result.session + sessionData: sessionGetReq.result.session, }); - } - //sessions.push(cursor.value); + }; if (!limit || sessions.length < limit) { cursor.continue(); } } - } + }; }); } @@ -516,13 +518,12 @@ export class Backend { return new Promise((resolve, reject) => { const req = objectStore.put({ senderCurve25519Key: session.senderKey, - sessionId: session.sessionId + sessionId: session.sessionId, }); req.onsuccess = resolve; req.onerror = reject; }); })); - } doTxn(mode, stores, func) { diff --git a/src/crypto/store/indexeddb-crypto-store.js b/src/crypto/store/indexeddb-crypto-store.js index 0eca4c373e0..59d68f8fc85 100644 --- a/src/crypto/store/indexeddb-crypto-store.js +++ b/src/crypto/store/indexeddb-crypto-store.js @@ -407,10 +407,13 @@ export default class IndexedDBCryptoStore { this._backendPromise.value().getEndToEndRooms(txn, func); } + // session backups + /** * Get the inbound group sessions that need to be backed up. * @param {integer} limit The maximum number of sessions to retrieve. 0 * for no limit. + * @returns {Promise} resolves to an array of inbound group sessions */ getSessionsNeedingBackup(limit) { return this._connect().then((backend) => { @@ -420,7 +423,8 @@ export default class IndexedDBCryptoStore { /** * Unmark sessions as needing to be backed up. - * @param {[object]} The sessions that need to be backed up. + * @param {[object]} sessions The sessions that need to be backed up. + * @returns {Promise} resolves when the sessions are unmarked */ unmarkSessionsNeedingBackup(sessions) { return this._connect().then((backend) => { @@ -430,8 +434,9 @@ export default class IndexedDBCryptoStore { /** * Mark sessions as needing to be backed up. - * @param {[object]} The sessions that need to be backed up. + * @param {[object]} sessions The sessions that need to be backed up. * @param {*} txn An active transaction. See doTxn(). (optional) + * @returns {Promise} resolves when the sessions are marked */ markSessionsNeedingBackup(sessions, txn) { return this._connect().then((backend) => { From 91fb7b0a7c4d530c33f2e47a769fc741302fa0a8 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Fri, 12 Oct 2018 12:03:51 -0400 Subject: [PATCH 31/70] fix unit tests for backup recovery --- spec/unit/crypto/backup.spec.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/spec/unit/crypto/backup.spec.js b/spec/unit/crypto/backup.spec.js index 7217f32266e..3a3ed3a97f3 100644 --- a/spec/unit/crypto/backup.spec.js +++ b/spec/unit/crypto/backup.spec.js @@ -446,8 +446,7 @@ describe("MegolmBackup", function() { return Promise.resolve(KEY_BACKUP_DATA); }; return client.restoreKeyBackups( - "qx37WTQrjZLz5tId/uBX9B3/okqAbV1ofl9UnHKno1eipByCpXleAAlAZoJgYnCDOQZD" - + "QWzo3luTSfkF9pU1mOILCbbouubs6TVeDyPfgGD9i86J8irHjA", + "EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d", ROOM_ID, SESSION_ID, ).then(() => { @@ -463,15 +462,14 @@ describe("MegolmBackup", function() { rooms: { [ROOM_ID]: { sessions: { - SESSION_ID: KEY_BACKUP_DATA, + [SESSION_ID]: KEY_BACKUP_DATA, }, }, }, }); }; return client.restoreKeyBackups( - "qx37WTQrjZLz5tId/uBX9B3/okqAbV1ofl9UnHKno1eipByCpXleAAlAZoJgYnCDOQZD" - + "QWzo3luTSfkF9pU1mOILCbbouubs6TVeDyPfgGD9i86J8irHjA", + "EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d", ).then(() => { return megolmDecryption.decryptEvent(ENCRYPTED_EVENT); }).then((res) => { From d49c0a1bcb5a53b5cf90bfbef968ce6e2ac12ccc Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Fri, 12 Oct 2018 14:28:31 -0400 Subject: [PATCH 32/70] more de-linting and fixing --- src/crypto/algorithms/megolm.js | 19 ++++++-- src/crypto/index.js | 4 +- .../store/indexeddb-crypto-store-backend.js | 1 - src/crypto/store/localStorage-crypto-store.js | 45 +++++++++++-------- src/crypto/store/memory-crypto-store.js | 10 +++-- 5 files changed, 51 insertions(+), 28 deletions(-) diff --git a/src/crypto/algorithms/megolm.js b/src/crypto/algorithms/megolm.js index af311e16bab..d1115f00e21 100644 --- a/src/crypto/algorithms/megolm.js +++ b/src/crypto/algorithms/megolm.js @@ -264,8 +264,8 @@ MegolmEncryption.prototype._prepareNewSession = async function() { ); if (this._crypto.backupInfo) { - // Not strictly necessary to wait for this - await this._crypto.backupGroupSession( + // don't wait for it to complete + this._crypto.backupGroupSession( this._roomId, this._olmDevice.deviceCurve25519Key, [], sessionId, key.key, ); @@ -849,7 +849,8 @@ MegolmDecryption.prototype.onRoomKeyEvent = function(event) { this._retryDecryption(senderKey, sessionId); }).then(() => { if (this._crypto.backupInfo) { - return this._crypto.backupGroupSession( + // don't wait for it to complete + this._crypto.backupGroupSession( content.room_id, senderKey, forwardingKeyChain, content.session_id, content.session_key, keysClaimed, exportFormat, @@ -972,6 +973,18 @@ MegolmDecryption.prototype.importRoomKey = function(session) { session.sender_claimed_keys, true, ).then(() => { + if (this._crypto.backupInfo) { + // don't wait for it to complete + this._crypto.backupGroupSession( + session.room_id, + session.sender_key, + session.forwarding_curve25519_key_chain, + session.session_id, + session.session_key, + session.sender_claimed_keys, + true, + ); + } // have another go at decrypting events sent with this session. this._retryDecryption(session.sender_key, session.session_id); }); diff --git a/src/crypto/index.js b/src/crypto/index.js index d45caabe396..20341c4b277 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -1061,7 +1061,7 @@ Crypto.prototype.backupGroupSession = async function( sessionId: sessionId, }]); - this._maybeSendKeyBackup(); + await this._maybeSendKeyBackup(); }; Crypto.prototype.backupAllGroupSessions = async function(version) { @@ -1077,7 +1077,7 @@ Crypto.prototype.backupAllGroupSessions = async function(version) { }, ); - this._maybeSendKeyBackup(); + await this._maybeSendKeyBackup(); }; /* eslint-disable valid-jsdoc */ //https://github.com/eslint/eslint/issues/7307 diff --git a/src/crypto/store/indexeddb-crypto-store-backend.js b/src/crypto/store/indexeddb-crypto-store-backend.js index d0bb9f1b7d8..d5b66c30f9c 100644 --- a/src/crypto/store/indexeddb-crypto-store-backend.js +++ b/src/crypto/store/indexeddb-crypto-store-backend.js @@ -501,7 +501,6 @@ export class Backend { const objectStore = txn.objectStore("sessions_needing_backup"); return Promise.all(sessions.map((session) => { return new Promise((resolve, reject) => { - console.log(session); const req = objectStore.delete([session.senderKey, session.sessionId]); req.onsuccess = resolve; req.onerror = reject; diff --git a/src/crypto/store/localStorage-crypto-store.js b/src/crypto/store/localStorage-crypto-store.js index 71a904fd805..cad6a7d6496 100644 --- a/src/crypto/store/localStorage-crypto-store.js +++ b/src/crypto/store/localStorage-crypto-store.js @@ -167,43 +167,52 @@ export default class LocalStorageCryptoStore extends MemoryCryptoStore { } getSessionsNeedingBackup(limit) { + const sessionsNeedingBackup + = getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {}; const sessions = []; - for (const session in getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP)) { - const senderKey = session.substr(0, 43); - const sessionId = session.substr(44); - getEndToEndInboundGroupSession(senderKey, sessionId, null, (sessionData) => { - sessions.push({ - senderKey: senderKey, - sessionId: sessionId, - sessionData: sessionData, - }); - }) - if (limit && session.length >= limit) { - break; + for (const session in sessionsNeedingBackup) { + if (Object.prototype.hasOwnProperty.call(sessionsNeedingBackup, session)) { + const senderKey = session.substr(0, 43); + const sessionId = session.substr(44); + this.getEndToEndInboundGroupSession( + senderKey, sessionId, null, + (sessionData) => { + sessions.push({ + senderKey: senderKey, + sessionId: sessionId, + sessionData: sessionData, + }); + }, + ); + if (limit && session.length >= limit) { + break; + } } } return Promise.resolve(sessions); } unmarkSessionsNeedingBackup(sessions) { - const sessionsNeedingBackup = getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {}; - for(const session of sessions) { + const sessionsNeedingBackup + = getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {}; + for (const session of sessions) { delete sessionsNeedingBackup[session.senderKey + '/' + session.sessionId]; } setJsonItem( - this.store, KEY_SESSION_NEEDING_BACKUP, sessionsNeedinBackup, + this.store, KEY_SESSIONS_NEEDING_BACKUP, sessionsNeedingBackup, ); return Promise.resolve(); } markSessionsNeedingBackup(sessions) { - const sessionsNeedingBackup = getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {}; - for(const session of sessions) { + const sessionsNeedingBackup + = getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {}; + for (const session of sessions) { sessionsNeedingBackup[session.senderKey + '/' + session.sessionId] = true; } setJsonItem( - this.store, KEY_SESSION_NEEDING_BACKUP, sessionsNeedinBackup, + this.store, KEY_SESSIONS_NEEDING_BACKUP, sessionsNeedingBackup, ); return Promise.resolve(); } diff --git a/src/crypto/store/memory-crypto-store.js b/src/crypto/store/memory-crypto-store.js index cd5ec5be4a9..6af31221917 100644 --- a/src/crypto/store/memory-crypto-store.js +++ b/src/crypto/store/memory-crypto-store.js @@ -315,15 +315,17 @@ export default class MemoryCryptoStore { } unmarkSessionsNeedingBackup(sessions) { - for(const session of sessions) { - delete this._sessionsNeedingBackup[session.senderKey + '/' + session.sessionId]; + for (const session of sessions) { + const sessionKey = session.senderKey + '/' + session.sessionId; + delete this._sessionsNeedingBackup[sessionKey]; } return Promise.resolve(); } markSessionsNeedingBackup(sessions) { - for(const session of sessions) { - this._sessionsNeedingBackup[session.senderKey + '/' + session.sessionId] = true; + for (const session of sessions) { + const sessionKey = session.senderKey + '/' + session.sessionId; + this._sessionsNeedingBackup[sessionKey] = true; } return Promise.resolve(); } From 40d0a823428e331063670b4978f07cc5fc7ca774 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Fri, 12 Oct 2018 15:45:48 -0400 Subject: [PATCH 33/70] remove accidental change to eslintrc --- .eslintrc.js | 1 - 1 file changed, 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index ae1826de529..fec2d7b5ada 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -16,7 +16,6 @@ module.exports = { }, extends: ["eslint:recommended", "google"], rules: { - "indent": ["error", 4], // rules we've always adhered to or now do "max-len": ["error", { code: 90, From 434ac86090836b1c1b2e8820d30e505aacd6f19b Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Fri, 19 Oct 2018 10:51:19 -0400 Subject: [PATCH 34/70] properly fill out the is_verified and first_message_index fields --- src/crypto/DeviceList.js | 41 +++++++++++++++++++++++++++++++++++++++- src/crypto/OlmDevice.js | 1 + src/crypto/index.js | 17 +++++++++++++---- 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/src/crypto/DeviceList.js b/src/crypto/DeviceList.js index fa55f2fa65b..c3a86ae1e8f 100644 --- a/src/crypto/DeviceList.js +++ b/src/crypto/DeviceList.js @@ -71,6 +71,9 @@ export default class DeviceList { // } this._devices = {}; + // 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 = {}; // loaded from storage in load() @@ -128,6 +131,19 @@ export default class DeviceList { deviceData.trackingStatus : {}; this._syncToken = deviceData ? deviceData.syncToken : null; } + this._userByIdentityKey = {}; + for (const user in this._devices) { + if (!this._devices.hasOwnProperty(user)) { + continue; + } + const userDevices = this._devices[user]; + for (const device in userDevices) { + if (!userDevices.hasOwnProperty(device)) { + continue; + } + this._userByIdentityKey[userDevices[device].senderKey] = user; + } + } }); }, ); @@ -357,13 +373,24 @@ export default class DeviceList { /** * Find a device by curve25519 identity key * - * @param {string} userId owner of the device + * @param {string} userId owner of the device (optional) * @param {string} algorithm encryption algorithm * @param {string} senderKey curve25519 key to match * * @return {module:crypto/deviceinfo?} */ getDeviceByIdentityKey(userId, algorithm, senderKey) { + if (arguments.length === 2) { + // if userId is omitted, shift the other arguments, and look up the + // userid + senderKey = algorithm; + algorithm = userId; + userId = this._userByIdentityKey[senderKey]; + if (!userId) { + return null; + } + } + if ( algorithm !== olmlib.OLM_ALGORITHM && algorithm !== olmlib.MEGOLM_ALGORITHM @@ -409,6 +436,12 @@ export default class DeviceList { */ storeDevicesForUser(u, devs) { this._devices[u] = devs; + for (const device in devs) { + if (!devs.hasOwnProperty(device)) { + continue; + } + this._userByIdentityKey[devs[device].senderKey] = u; + } this._dirty = true; } @@ -526,6 +559,12 @@ export default class DeviceList { */ _setRawStoredDevicesForUser(userId, devices) { this._devices[userId] = devices; + for (const device in devices) { + if (!devices.hasOwnProperty(device)) { + continue; + } + this._userByIdentityKey[devices[device].senderKey] = userId; + } } /** diff --git a/src/crypto/OlmDevice.js b/src/crypto/OlmDevice.js index 81884005494..74e46e2a404 100644 --- a/src/crypto/OlmDevice.js +++ b/src/crypto/OlmDevice.js @@ -1119,6 +1119,7 @@ OlmDevice.prototype.exportInboundGroupSession = function( "session_id": sessionId, "session_key": session.export_session(messageIndex), "forwarding_curve25519_key_chain": session.forwardingCurve25519KeyChain || [], + "first_known_index": session.first_known_index(), }; }); }; diff --git a/src/crypto/index.js b/src/crypto/index.js index 20341c4b277..2f1c39bd980 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -940,6 +940,7 @@ Crypto.prototype.exportRoomKeys = async function() { const sess = this._olmDevice.exportInboundGroupSession( s.senderKey, s.sessionId, s.sessionData, ); + delete sess.first_known_index; sess.algorithm = olmlib.MEGOLM_ALGORITHM; exportedSessions.push(sess); }); @@ -1002,13 +1003,21 @@ Crypto.prototype._maybeSendKeyBackup = async function() { sessionData.algorithm = olmlib.MEGOLM_ALGORITHM; delete sessionData.session_id; delete sessionData.room_id; + const firstKnownIndex = sessionData.first_known_index; + delete sessionData.first_known_index; const encrypted = this.backupKey.encrypt(JSON.stringify(sessionData)); + const forwardedCount = + (sessionData.forwardingCurve25519KeyChain || []).length; + + const device = this._deviceList.getDeviceByIdentityKey( + olmlib.MEGOLM_ALGORITHM, session.senderKey, + ); + data[roomId]['sessions'][session.sessionId] = { - first_message_index: 1, // FIXME - forwarded_count: - (sessionData.forwardingCurve25519KeyChain || []).length, - is_verified: false, // FIXME: how do we determine this? + first_message_index: firstKnownIndex, + forwarded_count: forwardedCount, + is_verified: !!(device && device.isVerified()), session_data: encrypted, }; } From 322ef1fd637003889fe3825957c66d3e8c39abf2 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Mon, 22 Oct 2018 11:28:16 -0400 Subject: [PATCH 35/70] update backup algorithm name to agree with the proposal --- src/crypto/olmlib.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crypto/olmlib.js b/src/crypto/olmlib.js index f03714f1644..bbe9420362b 100644 --- a/src/crypto/olmlib.js +++ b/src/crypto/olmlib.js @@ -38,7 +38,7 @@ module.exports.MEGOLM_ALGORITHM = "m.megolm.v1.aes-sha2"; /** * matrix algorithm tag for megolm backups */ -module.exports.MEGOLM_BACKUP_ALGORITHM = "m.megolm_backup.v1"; +module.exports.MEGOLM_BACKUP_ALGORITHM = "m.megolm_backup.v1.curve25519-aes-sha2"; /** From 40cb37e824b9a6fd92c92c9770af3e34242ed583 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 24 Oct 2018 16:58:48 +0100 Subject: [PATCH 36/70] Update to Olm 3 --- README.md | 4 ++-- travis.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 20dd640b66c..be2fa0f8633 100644 --- a/README.md +++ b/README.md @@ -319,13 +319,13 @@ To provide the Olm library in a browser application: To provide the Olm library in a node.js application: - * ``npm install https://matrix.org/packages/npm/olm/olm-2.2.2.tgz`` + * ``npm install https://matrix.org/packages/npm/olm/olm-3.0.0.tgz`` (replace the URL with the latest version you want to use from https://matrix.org/packages/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 +can use ``npm install https://matrix.org/packages/npm/olm/olm-3.0.0.tgz --save-optional`` (if your application also works without e2e crypto enabled) or ``--save`` (if it doesn't) to do so. diff --git a/travis.sh b/travis.sh index 4c47f00e719..ddf4a790ecd 100755 --- a/travis.sh +++ b/travis.sh @@ -5,7 +5,7 @@ set -ex npm run lint # install Olm so that we can run the crypto tests. -npm install https://matrix.org/packages/npm/olm/olm-2.3.0.tgz +npm install https://matrix.org/packages/npm/olm/olm-3.0.0.tgz npm run test From b3bb99d76a8a0ca1984c15f73ce7d9c270351ac6 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 25 Oct 2018 19:11:43 +0100 Subject: [PATCH 37/70] Stop client after backup tests --- spec/unit/crypto/backup.spec.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/unit/crypto/backup.spec.js b/spec/unit/crypto/backup.spec.js index 3a3ed3a97f3..79f956fb008 100644 --- a/spec/unit/crypto/backup.spec.js +++ b/spec/unit/crypto/backup.spec.js @@ -441,6 +441,10 @@ describe("MegolmBackup", function() { return client.initCrypto(); }); + afterEach(function() { + client.stopClient(); + }); + it('can restore from backup', function() { client._http.authedRequest = function() { return Promise.resolve(KEY_BACKUP_DATA); From a6bf40d4e2e408ea937273be920e9def6e583c4c Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 25 Oct 2018 19:21:29 +0100 Subject: [PATCH 38/70] We can always import these now --- spec/unit/crypto/backup.spec.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/spec/unit/crypto/backup.spec.js b/spec/unit/crypto/backup.spec.js index 79f956fb008..511f819986b 100644 --- a/spec/unit/crypto/backup.spec.js +++ b/spec/unit/crypto/backup.spec.js @@ -29,13 +29,8 @@ import MemoryCryptoStore from '../../../lib/crypto/store/memory-crypto-store.js' 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'); -} +import OlmDevice from '../../../lib/crypto/OlmDevice'; +import Crypto from '../../../lib/crypto'; const Olm = global.Olm; From 0e26247b5312aa386e36653c7d5da9f7b398e610 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 26 Oct 2018 12:08:55 +0100 Subject: [PATCH 39/70] Speed up time rather than increasing timeouts --- spec/unit/crypto/backup.spec.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/spec/unit/crypto/backup.spec.js b/spec/unit/crypto/backup.spec.js index 511f819986b..64b85c78712 100644 --- a/spec/unit/crypto/backup.spec.js +++ b/spec/unit/crypto/backup.spec.js @@ -122,6 +122,7 @@ describe("MegolmBackup", function() { describe("backup", function() { let mockBaseApis; + let realSetTimeout; beforeEach(function() { mockBaseApis = {}; @@ -135,6 +136,18 @@ describe("MegolmBackup", function() { }); 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() { @@ -175,7 +188,6 @@ describe("MegolmBackup", function() { }); it('sends backups to the server', function() { - this.timeout(12000); // eslint-disable-line no-invalid-this const groupSession = new Olm.OutboundGroupSession(); groupSession.create(); const ibGroupSession = new Olm.InboundGroupSession(); @@ -281,7 +293,6 @@ describe("MegolmBackup", function() { }); it('retries when a backup fails', function() { - this.timeout(12000); // eslint-disable-line no-invalid-this const groupSession = new Olm.OutboundGroupSession(); groupSession.create(); const ibGroupSession = new Olm.InboundGroupSession(); From 563e6b3cdd29a6a8174a0eb65aff10c99d00afa1 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 26 Oct 2018 13:23:37 +0100 Subject: [PATCH 40/70] Fix jsdoc --- src/crypto/store/indexeddb-crypto-store.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/crypto/store/indexeddb-crypto-store.js b/src/crypto/store/indexeddb-crypto-store.js index 0cfa86b5afd..fb49ee1e8fa 100644 --- a/src/crypto/store/indexeddb-crypto-store.js +++ b/src/crypto/store/indexeddb-crypto-store.js @@ -437,7 +437,7 @@ export default class IndexedDBCryptoStore { /** * Unmark sessions as needing to be backed up. - * @param {[object]} sessions The sessions that need to be backed up. + * @param {Array} sessions The sessions that need to be backed up. * @returns {Promise} resolves when the sessions are unmarked */ unmarkSessionsNeedingBackup(sessions) { @@ -448,7 +448,7 @@ export default class IndexedDBCryptoStore { /** * Mark sessions as needing to be backed up. - * @param {[object]} sessions The sessions that need to be backed up. + * @param {Array} sessions The sessions that need to be backed up. * @param {*} txn An active transaction. See doTxn(). (optional) * @returns {Promise} resolves when the sessions are marked */ From 3b2f2f922e45ad7eb9c6801f52ff1973c69159d2 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 26 Oct 2018 18:49:05 +0100 Subject: [PATCH 41/70] Bump db version --- src/crypto/store/indexeddb-crypto-store-backend.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crypto/store/indexeddb-crypto-store-backend.js b/src/crypto/store/indexeddb-crypto-store-backend.js index c27c48e3b5a..ac21e6f0716 100644 --- a/src/crypto/store/indexeddb-crypto-store-backend.js +++ b/src/crypto/store/indexeddb-crypto-store-backend.js @@ -20,7 +20,7 @@ import Promise from 'bluebird'; import logger from '../../logger'; import utils from '../../utils'; -export const VERSION = 6; +export const VERSION = 7; /** * Implementation of a CryptoStore which is backed by an existing From e51d2dd36abd7325c9358d0136a91b093e77a564 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 30 Oct 2018 11:45:19 +0000 Subject: [PATCH 42/70] 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 --- src/client.js | 2 -- src/crypto/index.js | 28 +++++++++++++++++----------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/client.js b/src/client.js index eb964650a28..9bb25903887 100644 --- a/src/client.js +++ b/src/client.js @@ -839,8 +839,6 @@ MatrixClient.prototype.enableKeyBackup = function(info) { this._crypto.backupKey.set_recipient_key(info.auth_data.public_key); this.emit('keyBackupStatus', true); - - this._crypto._maybeSendKeyBackup(); }; /** diff --git a/src/crypto/index.js b/src/crypto/index.js index 655a801dc48..7f2bfdb16d7 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -970,16 +970,21 @@ Crypto.prototype.importRoomKeys = function(keys) { ); }; -Crypto.prototype._maybeSendKeyBackup = async function() { +Crypto.prototype._maybeSendKeyBackup = async function(delay, retry) { + if (retry === undefined) retry = true; + if (!this._sendingBackups) { this._sendingBackups = true; try { - // wait between 0 and 10 seconds, to avoid backup requests from - // different clients hitting the server all at the same time when a - // new key is sent - await new Promise((resolve, reject) => { - setTimeout(resolve, Math.random() * 10000); - }); + if (delay === undefined) { + // by default, wait between 0 and 10 seconds, to avoid backup + // requests from different clients hitting the server all at + // the same time when a new key is sent + delay = Math.random() * 10000; + } + if (delay > 0) { + await Promise.delay(delay); + } let numFailures = 0; // number of consecutive failures while (1) { if (!this.backupKey) { @@ -1034,10 +1039,11 @@ Crypto.prototype._maybeSendKeyBackup = async function() { console.log("send failed", err); if (err.httpStatus === 400 || err.httpStatus === 403 - || err.httpStatus === 401) { + || err.httpStatus === 401 + || !retry) { // retrying probably won't help much, so we should give up // FIXME: disable backups? - return; + throw err; } } if (numFailures) { @@ -1076,7 +1082,7 @@ Crypto.prototype.backupGroupSession = async function( Crypto.prototype.backupAllGroupSessions = async function(version) { await this._cryptoStore.doTxn( 'readwrite', - [IndexedDBCryptoStore.STORE_SESSIONS, IndexedDBCryptoStore.STORE_BACKUP], + [IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS, IndexedDBCryptoStore.STORE_BACKUP], (txn) => { this._cryptoStore.getAllEndToEndInboundGroupSessions(txn, (session) => { if (session !== null) { @@ -1086,7 +1092,7 @@ Crypto.prototype.backupAllGroupSessions = async function(version) { }, ); - await this._maybeSendKeyBackup(); + await this._maybeSendKeyBackup(0, false); }; /* eslint-disable valid-jsdoc */ //https://github.com/eslint/eslint/issues/7307 From a2430dbc5387ed784e6f392c168c2ac5d3fa83ed Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 30 Oct 2018 12:29:44 +0000 Subject: [PATCH 43/70] Fix DeviceList index of users by identity key Was causing all keys to be send as unverified --- src/crypto/DeviceList.js | 45 ++++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/src/crypto/DeviceList.js b/src/crypto/DeviceList.js index e85cb7ef321..9416dea7a61 100644 --- a/src/crypto/DeviceList.js +++ b/src/crypto/DeviceList.js @@ -142,7 +142,10 @@ export default class DeviceList { if (!userDevices.hasOwnProperty(device)) { continue; } - this._userByIdentityKey[userDevices[device].senderKey] = user; + const identityKey = userDevices[device].keys['curve25519:'+device]; + if (identityKey !== undefined) { + this._userByIdentityKey[identityKey] = user; + } } } }); @@ -442,12 +445,22 @@ export default class DeviceList { * @param {Object} devs New device info for user */ storeDevicesForUser(u, devs) { - this._devices[u] = devs; - for (const device in devs) { - if (!devs.hasOwnProperty(device)) { - continue; + // 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._userByIdentityKey[devs[device].senderKey] = u; + } + + this._devices[u] = devs; + + // add enw ones + for (const [deviceId, dev] of Object.entries(devs)) { + const identityKey = dev.keys['curve25519:'+deviceId]; + + this._userByIdentityKey[identityKey] = u; } this._dirty = true; } @@ -565,12 +578,22 @@ export default class DeviceList { * @param {Object} devices deviceId->{object} the new devices */ _setRawStoredDevicesForUser(userId, devices) { - this._devices[userId] = devices; - for (const device in devices) { - if (!devices.hasOwnProperty(device)) { - continue; + // 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._userByIdentityKey[devices[device].senderKey] = userId; + } + + 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; } } From 2814932845a26ce2b84ba3c82900f96de664cb80 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 30 Oct 2018 12:36:03 +0000 Subject: [PATCH 44/70] lint --- src/crypto/DeviceList.js | 6 +++--- src/crypto/index.js | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/crypto/DeviceList.js b/src/crypto/DeviceList.js index 9416dea7a61..fa1d1e18a25 100644 --- a/src/crypto/DeviceList.js +++ b/src/crypto/DeviceList.js @@ -142,9 +142,9 @@ export default class DeviceList { if (!userDevices.hasOwnProperty(device)) { continue; } - const identityKey = userDevices[device].keys['curve25519:'+device]; - if (identityKey !== undefined) { - this._userByIdentityKey[identityKey] = user; + const idKey = userDevices[device].keys['curve25519:'+device]; + if (idKey !== undefined) { + this._userByIdentityKey[idKey] = user; } } } diff --git a/src/crypto/index.js b/src/crypto/index.js index 7f2bfdb16d7..3918ec862eb 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -1082,7 +1082,10 @@ Crypto.prototype.backupGroupSession = async function( Crypto.prototype.backupAllGroupSessions = async function(version) { await this._cryptoStore.doTxn( 'readwrite', - [IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS, IndexedDBCryptoStore.STORE_BACKUP], + [ + IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS, + IndexedDBCryptoStore.STORE_BACKUP, + ], (txn) => { this._cryptoStore.getAllEndToEndInboundGroupSessions(txn, (session) => { if (session !== null) { From 8ab84dee165d743bba049aa3c9e8c07ac6db3da3 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 31 Oct 2018 17:40:17 +0000 Subject: [PATCH 45/70] PR feedback 1/n --- spec/unit/crypto/backup.spec.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/spec/unit/crypto/backup.spec.js b/spec/unit/crypto/backup.spec.js index 64b85c78712..5cf3b84bc1e 100644 --- a/spec/unit/crypto/backup.spec.js +++ b/spec/unit/crypto/backup.spec.js @@ -262,7 +262,8 @@ describe("MegolmBackup", function() { client._http.authedRequest = function( callback, method, path, queryParams, data, opts, ) { - expect(++numCalls <= 1); + ++numCalls; + expect(numCalls <= 1); if (numCalls >= 2) { // exit out of retry loop if there's something wrong reject(new Error("authedRequest called too many timmes")); @@ -285,10 +286,9 @@ describe("MegolmBackup", function() { groupSession.session_id(), groupSession.session_key(), ); - }) - .then(() => { - expect(numCalls).toBe(1); - }); + }).then(() => { + expect(numCalls).toBe(1); + }); }); }); @@ -367,7 +367,8 @@ describe("MegolmBackup", function() { client._http.authedRequest = function( callback, method, path, queryParams, data, opts, ) { - expect(++numCalls <= 2); + ++numCalls; + expect(numCalls <= 2); if (numCalls >= 3) { // exit out of retry loop if there's something wrong reject(new Error("authedRequest called too many timmes")); @@ -396,10 +397,9 @@ describe("MegolmBackup", function() { groupSession.session_id(), groupSession.session_key(), ); - }) - .then(() => { - expect(numCalls).toBe(2); - }); + }).then(() => { + expect(numCalls).toBe(2); + }); }); }); }); From c6ad0665b50c5947c78bf8180cb47d569b382682 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 31 Oct 2018 18:03:40 +0000 Subject: [PATCH 46/70] factor out duplicated test code --- spec/unit/crypto/backup.spec.js | 80 +++++++++++++-------------------- 1 file changed, 30 insertions(+), 50 deletions(-) diff --git a/spec/unit/crypto/backup.spec.js b/spec/unit/crypto/backup.spec.js index 5cf3b84bc1e..c83949cf074 100644 --- a/spec/unit/crypto/backup.spec.js +++ b/spec/unit/crypto/backup.spec.js @@ -77,6 +77,34 @@ const KEY_BACKUP_DATA = { }, }; +function makeTestClient(sessionStore, cryptoStore) { + const scheduler = [ + "getQueueForEvent", "queueEvent", "removeEventFromQueue", + "setProcessFunction", + ].reduce((r, k) => {r[k] = expect.createSpy(); 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] = expect.createSpy(); return r;}, {}); + store.getSavedSync = expect.createSpy().andReturn(Promise.resolve(null)); + store.getSavedSyncToken = expect.createSpy().andReturn(Promise.resolve(null)); + store.setSyncData = expect.createSpy().andReturn(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, + }); +}; + describe("MegolmBackup", function() { if (!global.Olm) { console.warn('Not running megolm backup unit tests: libolm not present'); @@ -193,31 +221,7 @@ describe("MegolmBackup", function() { const ibGroupSession = new Olm.InboundGroupSession(); ibGroupSession.create(groupSession.session_key()); - const scheduler = [ - "getQueueForEvent", "queueEvent", "removeEventFromQueue", - "setProcessFunction", - ].reduce((r, k) => {r[k] = expect.createSpy(); 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] = expect.createSpy(); return r;}, {}); - store.getSavedSync = expect.createSpy().andReturn(Promise.resolve(null)); - store.getSavedSyncToken = expect.createSpy().andReturn(Promise.resolve(null)); - store.setSyncData = expect.createSpy().andReturn(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, - }); + const client = makeTestClient(sessionStore, cryptoStore); megolmDecryption = new MegolmDecryption({ userId: '@user:id', @@ -408,31 +412,7 @@ describe("MegolmBackup", function() { let client; beforeEach(function() { - const scheduler = [ - "getQueueForEvent", "queueEvent", "removeEventFromQueue", - "setProcessFunction", - ].reduce((r, k) => {r[k] = expect.createSpy(); 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] = expect.createSpy(); return r;}, {}); - store.getSavedSync = expect.createSpy().andReturn(Promise.resolve(null)); - store.getSavedSyncToken = expect.createSpy().andReturn(Promise.resolve(null)); - store.setSyncData = expect.createSpy().andReturn(Promise.resolve(null)); - 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, - }); + client = makeTestClient(sessionStore, cryptoStore); megolmDecryption = new MegolmDecryption({ userId: '@user:id', From 2b46c560c7fb6e63d6df1001979fd413e405d2e5 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 31 Oct 2018 18:07:12 +0000 Subject: [PATCH 47/70] Add crypto. prefix to keyBackupStatus event --- src/client.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/client.js b/src/client.js index 9bb25903887..84c42ae185b 100644 --- a/src/client.js +++ b/src/client.js @@ -838,7 +838,7 @@ MatrixClient.prototype.enableKeyBackup = function(info) { this._crypto.backupKey = new global.Olm.PkEncryption(); this._crypto.backupKey.set_recipient_key(info.auth_data.public_key); - this.emit('keyBackupStatus', true); + this.emit('crypto.keyBackupStatus', true); }; /** @@ -853,7 +853,7 @@ MatrixClient.prototype.disableKeyBackup = function() { if (this._crypto.backupKey) this._crypto.backupKey.free(); this._crypto.backupKey = null; - this.emit('keyBackupStatus', false); + this.emit('crypto.keyBackupStatus', false); }; /** @@ -4065,10 +4065,10 @@ module.exports.CRYPTO_ENABLED = CRYPTO_ENABLED; /** * Fires whenever the status of e2e key backup changes, as returned by getKeyBackupEnabled() - * @event module:client~MatrixClient#"keyBackupStatus" + * @event module:client~MatrixClient#"crypto.keyBackupStatus" * @param {bool} enabled true if key backup has been enabled, otherwise false * @example - * matrixClient.on("keyBackupStatus", function(enabled){ + * matrixClient.on("crypto.keyBackupStatus", function(enabled){ * if (enabled) { * [...] * } From c5e7bedb37bdcd4fe6a659b6feace7d185046035 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 31 Oct 2018 18:08:53 +0000 Subject: [PATCH 48/70] Conclusion: no, it shouldn't --- src/client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client.js b/src/client.js index 84c42ae185b..9f66b789800 100644 --- a/src/client.js +++ b/src/client.js @@ -897,7 +897,7 @@ MatrixClient.prototype.createKeyBackupVersion = function(info) { const data = { algorithm: info.algorithm, - auth_data: info.auth_data, // FIXME: should this be cloned? + auth_data: info.auth_data, }; return this._crypto._signObject(data.auth_data).then(() => { return this._http.authedRequest( From f5846b89ea0ee9487a0b87e27fd48cc34f843094 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 31 Oct 2018 18:18:41 +0000 Subject: [PATCH 49/70] More modern loop syntax --- src/crypto/DeviceList.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/crypto/DeviceList.js b/src/crypto/DeviceList.js index fa1d1e18a25..417aa0dfb5f 100644 --- a/src/crypto/DeviceList.js +++ b/src/crypto/DeviceList.js @@ -133,15 +133,9 @@ export default class DeviceList { this._syncToken = deviceData ? deviceData.syncToken : null; } this._userByIdentityKey = {}; - for (const user in this._devices) { - if (!this._devices.hasOwnProperty(user)) { - continue; - } + for (const user of Object.keys(this._devices)) { const userDevices = this._devices[user]; - for (const device in userDevices) { - if (!userDevices.hasOwnProperty(device)) { - continue; - } + for (const device of Object.keys(userDevices)) { const idKey = userDevices[device].keys['curve25519:'+device]; if (idKey !== undefined) { this._userByIdentityKey[idKey] = user; From 6de213483cbe3ad23fbcd93be9e429378d4da755 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 31 Oct 2018 18:31:56 +0000 Subject: [PATCH 50/70] Change getDeviceByIdentityKey() to just the 2 arg version --- src/crypto/DeviceList.js | 15 ++++----------- src/crypto/index.js | 2 +- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/src/crypto/DeviceList.js b/src/crypto/DeviceList.js index 417aa0dfb5f..57bc74a4fab 100644 --- a/src/crypto/DeviceList.js +++ b/src/crypto/DeviceList.js @@ -377,22 +377,15 @@ export default class DeviceList { /** * Find a device by curve25519 identity key * - * @param {string} userId owner of the device (optional) * @param {string} algorithm encryption algorithm * @param {string} senderKey curve25519 key to match * * @return {module:crypto/deviceinfo?} */ - getDeviceByIdentityKey(userId, algorithm, senderKey) { - if (arguments.length === 2) { - // if userId is omitted, shift the other arguments, and look up the - // userid - senderKey = algorithm; - algorithm = userId; - userId = this._userByIdentityKey[senderKey]; - if (!userId) { - return null; - } + getDeviceByIdentityKey(algorithm, senderKey) { + const userId = this._userByIdentityKey[senderKey]; + if (!userId) { + return null; } if ( diff --git a/src/crypto/index.js b/src/crypto/index.js index 3918ec862eb..97244f7b372 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -713,7 +713,7 @@ Crypto.prototype.getEventSenderDeviceInfo = function(event) { // identity key of the device which set up the Megolm session. const device = this._deviceList.getDeviceByIdentityKey( - event.getSender(), algorithm, senderKey, + algorithm, senderKey, ); if (device === null) { From 5c5ce0dfe306837ebc00ec1e017d4f2411e48c4f Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 31 Oct 2018 18:32:48 +0000 Subject: [PATCH 51/70] Typo --- src/crypto/DeviceList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crypto/DeviceList.js b/src/crypto/DeviceList.js index 57bc74a4fab..751982b4237 100644 --- a/src/crypto/DeviceList.js +++ b/src/crypto/DeviceList.js @@ -443,7 +443,7 @@ export default class DeviceList { this._devices[u] = devs; - // add enw ones + // add new ones for (const [deviceId, dev] of Object.entries(devs)) { const identityKey = dev.keys['curve25519:'+deviceId]; From db2897cf1edf2085f2c83ce887f9ef022fbe3dbd Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 31 Oct 2018 18:33:31 +0000 Subject: [PATCH 52/70] Remove spurious interlopers --- src/crypto/OlmDevice.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/crypto/OlmDevice.js b/src/crypto/OlmDevice.js index 0b763fc1c28..d7f182881c6 100644 --- a/src/crypto/OlmDevice.js +++ b/src/crypto/OlmDevice.js @@ -81,17 +81,6 @@ function OlmDevice(sessionStore, cryptoStore) { this.deviceEd25519Key = null; this._maxOneTimeKeys = null; - // track which of our other devices (if any) have cross-signed this device - // XXX: this should probably have a single source of truth in the /devices - // API store or whatever we use to track our self-signed devices. - this.crossSelfSigs = []; - - // track whether we have already suggested to the user that they should - // restore their keys from backup or by cross-signing the device. - // We use this to avoid repeatedly emitting the suggestion event. - // XXX: persist this somewhere! - this.suggestedKeyRestore = false; - // we don't bother stashing outboundgroupsessions in the sessionstore - // instead we keep them here. this._outboundGroupSessionStore = {}; From c77ecad9a5a44bdf3fc9f2e61b24d28d4eb50e42 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 31 Oct 2018 18:34:49 +0000 Subject: [PATCH 53/70] clarify comment --- src/crypto/algorithms/megolm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crypto/algorithms/megolm.js b/src/crypto/algorithms/megolm.js index e36f4678396..cf31e305651 100644 --- a/src/crypto/algorithms/megolm.js +++ b/src/crypto/algorithms/megolm.js @@ -850,7 +850,7 @@ MegolmDecryption.prototype.onRoomKeyEvent = function(event) { this._retryDecryption(senderKey, sessionId); }).then(() => { if (this._crypto.backupInfo) { - // don't wait for it to complete + // don't wait for the keys to be backed up for the server this._crypto.backupGroupSession( content.room_id, senderKey, forwardingKeyChain, content.session_id, content.session_key, keysClaimed, From 7c0b910d7a6a77e44c6eeb0f88edf84f59bc83f0 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 31 Oct 2018 18:36:55 +0000 Subject: [PATCH 54/70] remove unnecessary isFinite check --- src/crypto/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crypto/index.js b/src/crypto/index.js index 97244f7b372..62dab61859d 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -206,7 +206,7 @@ Crypto.prototype._checkAndStartKeyBackup = async function() { backupInfo = await this._baseApis.getKeyBackupVersion(); } catch (e) { console.log("Error checking for active key backup", e); - if (Number.isFinite(e.httpStatus) && e.httpStatus / 100 === 4) { + if (e.httpStatus / 100 === 4) { // well that's told us. we won't try again. this._checkedForBackup = true; } From 63e9f794c7aefa7a26fb3b991c4735f6da8ea202 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 31 Oct 2018 18:37:38 +0000 Subject: [PATCH 55/70] Remove unnecessary if --- src/crypto/index.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/crypto/index.js b/src/crypto/index.js index 62dab61859d..0244862715e 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -982,9 +982,7 @@ Crypto.prototype._maybeSendKeyBackup = async function(delay, retry) { // the same time when a new key is sent delay = Math.random() * 10000; } - if (delay > 0) { - await Promise.delay(delay); - } + await Promise.delay(delay); let numFailures = 0; // number of consecutive failures while (1) { if (!this.backupKey) { From 2f219f83dbba1167465d85fb9f699117728219e6 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 31 Oct 2018 18:46:02 +0000 Subject: [PATCH 56/70] Catch exceptions from backupGroupSession() --- src/crypto/algorithms/megolm.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/crypto/algorithms/megolm.js b/src/crypto/algorithms/megolm.js index cf31e305651..c13ca4dfae3 100644 --- a/src/crypto/algorithms/megolm.js +++ b/src/crypto/algorithms/megolm.js @@ -269,7 +269,11 @@ MegolmEncryption.prototype._prepareNewSession = async function() { this._crypto.backupGroupSession( this._roomId, this._olmDevice.deviceCurve25519Key, [], sessionId, key.key, - ); + ).catch((e) => { + // This throws if the upload failed, but this is fine + // since it will have written it to the db and will retry. + console.log("Failed to back up group session", e); + }); } return new OutboundSessionInfo(sessionId); @@ -855,7 +859,11 @@ MegolmDecryption.prototype.onRoomKeyEvent = function(event) { content.room_id, senderKey, forwardingKeyChain, content.session_id, content.session_key, keysClaimed, exportFormat, - ); + ).catch((e) => { + // This throws if the upload failed, but this is fine + // since it will have written it to the db and will retry. + console.log("Failed to back up group session", e); + }); } }).catch((e) => { logger.error(`Error handling m.room_key_event: ${e}`); @@ -984,7 +992,11 @@ MegolmDecryption.prototype.importRoomKey = function(session) { session.session_key, session.sender_claimed_keys, true, - ); + ).catch((e) => { + // This throws if the upload failed, but this is fine + // since it will have written it to the db and will retry. + console.log("Failed to back up group session", e); + }); } // have another go at decrypting events sent with this session. this._retryDecryption(session.sender_key, session.session_id); From 5e9885946ff9b8dd2b1c340761f8740b5f2c2090 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 31 Oct 2018 19:36:30 +0000 Subject: [PATCH 57/70] random double linebreak --- src/crypto/recoverykey.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/crypto/recoverykey.js b/src/crypto/recoverykey.js index bb85697e86e..c9ff75f214b 100644 --- a/src/crypto/recoverykey.js +++ b/src/crypto/recoverykey.js @@ -32,7 +32,6 @@ export function encodeRecoveryKey(key) { buf[buf.length - 1] = parity; const base58key = bs58.encode(buf); - return base58key.match(/.{1,4}/g).join(" "); } From 2af564324373fbf04a1baf8fa4726ee09bb3cf10 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 31 Oct 2018 19:37:19 +0000 Subject: [PATCH 58/70] Clarify comment --- src/crypto/recoverykey.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crypto/recoverykey.js b/src/crypto/recoverykey.js index c9ff75f214b..d4d949f426f 100644 --- a/src/crypto/recoverykey.js +++ b/src/crypto/recoverykey.js @@ -17,7 +17,7 @@ limitations under the License. import bs58 from 'bs58'; // picked arbitrarily but to try & avoid clashing with any bitcoin ones -// (also base58 encoded, albeit with a lot of hashing) +// (which are also base58 encoded, but bitcoin's involve a lot more hashing) const OLM_RECOVERY_KEY_PREFIX = [0x8B, 0x01]; export function encodeRecoveryKey(key) { From c7a0c1402cc0f172829d8aae27793644a23ffce5 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 31 Oct 2018 19:39:07 +0000 Subject: [PATCH 59/70] refer to getAllEndToEndInboundGroupSessions for magic numbers --- src/crypto/store/localStorage-crypto-store.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/crypto/store/localStorage-crypto-store.js b/src/crypto/store/localStorage-crypto-store.js index b88b8f9bd29..65d94eda50b 100644 --- a/src/crypto/store/localStorage-crypto-store.js +++ b/src/crypto/store/localStorage-crypto-store.js @@ -175,6 +175,7 @@ export default class LocalStorageCryptoStore extends MemoryCryptoStore { for (const session in sessionsNeedingBackup) { if (Object.prototype.hasOwnProperty.call(sessionsNeedingBackup, session)) { + // see getAllEndToEndInboundGroupSessions for the magic number explanations const senderKey = session.substr(0, 43); const sessionId = session.substr(44); this.getEndToEndInboundGroupSession( From 0477f354c9c244268b8a0b9e71158a347c6db6f2 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 31 Oct 2018 19:45:29 +0000 Subject: [PATCH 60/70] Fix key forwarded count It's exported in snake case --- src/crypto/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crypto/index.js b/src/crypto/index.js index 0244862715e..6fce09bcdca 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -1011,7 +1011,7 @@ Crypto.prototype._maybeSendKeyBackup = async function(delay, retry) { const encrypted = this.backupKey.encrypt(JSON.stringify(sessionData)); const forwardedCount = - (sessionData.forwardingCurve25519KeyChain || []).length; + (sessionData.forwarding_curve25519_key_chain || []).length; const device = this._deviceList.getDeviceByIdentityKey( olmlib.MEGOLM_ALGORITHM, session.senderKey, From 29d92d3e81aa7600c20c083014290b6d7c32f3e8 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 31 Oct 2018 20:05:21 +0000 Subject: [PATCH 61/70] Lint --- spec/unit/crypto/backup.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit/crypto/backup.spec.js b/spec/unit/crypto/backup.spec.js index c83949cf074..bf9fe8eddd1 100644 --- a/spec/unit/crypto/backup.spec.js +++ b/spec/unit/crypto/backup.spec.js @@ -103,7 +103,7 @@ function makeTestClient(sessionStore, cryptoStore) { sessionStore: sessionStore, cryptoStore: cryptoStore, }); -}; +} describe("MegolmBackup", function() { if (!global.Olm) { From 379f290b8bfebda2f990b212e5f8a304b3b0b228 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 13 Nov 2018 14:49:56 +0000 Subject: [PATCH 62/70] Add package-lock.json to force base-x to version 3.0.4 because 3.0.5 breaks the build by exporting ES6. --- package-lock.json | 6856 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 6856 insertions(+) create mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000000..b56c65f2f22 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6856 @@ +{ + "name": "matrix-js-sdk", + "version": "0.12.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "abbrev": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", + "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", + "dev": true + }, + "accessory": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/accessory/-/accessory-1.1.0.tgz", + "integrity": "sha1-eDPpg5oy3tdtJgIfNqQXB6Ug9ZM=", + "dev": true, + "requires": { + "ap": "~0.2.0", + "balanced-match": "~0.2.0", + "dot-parts": "~1.0.0" + }, + "dependencies": { + "balanced-match": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.2.1.tgz", + "integrity": "sha1-e8ZYtL7WHu5CStdPdfXD4sTfPMc=", + "dev": true + } + } + }, + "acorn": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.4.tgz", + "integrity": "sha512-VY4i5EKSKkofY2I+6QLTbTTN/UvEQPCo6eiwzzSaSWfpaDhOmStMCMod6wmuPciNq+XS0faCglFu2lHZpdHUtg==", + "dev": true + }, + "acorn-dynamic-import": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz", + "integrity": "sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==", + "dev": true + }, + "acorn-jsx": { + "version": "3.0.1", + "resolved": "http://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, + "requires": { + "acorn": "^3.0.4" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "http://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + } + } + }, + "acorn-node": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.6.2.tgz", + "integrity": "sha512-rIhNEZuNI8ibQcL7ANm/mGyPukIaZsRNX9psFNQURyJW0nu6k8wjSDld20z6v2mDBWqX13pIEnk9gGZJHIlEXg==", + "dev": true, + "requires": { + "acorn": "^6.0.2", + "acorn-dynamic-import": "^4.0.0", + "acorn-walk": "^6.1.0", + "xtend": "^4.0.1" + } + }, + "acorn-walk": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.1.tgz", + "integrity": "sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==", + "dev": true + }, + "ajv": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.5.tgz", + "integrity": "sha512-7q7gtRQDJSyuEHjuVgHoUa2VuemFiCMrfQc9Tc08XTAc4Zj/5U1buQJ0HU6i7fKjXU09SVgSmxa4sLvuvS8Iyg==", + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", + "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", + "dev": true + }, + "align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "dev": true, + "requires": { + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + } + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true, + "optional": true + }, + "another-json": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/another-json/-/another-json-0.2.0.tgz", + "integrity": "sha1-tfQBnJc7bdXGUGotk0acttMq7tw=" + }, + "ansi-escapes": { + "version": "1.4.0", + "resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "anymatch": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", + "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", + "dev": true, + "requires": { + "micromatch": "^2.1.5", + "normalize-path": "^2.0.0" + } + }, + "ap": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/ap/-/ap-0.2.0.tgz", + "integrity": "sha1-rglCYAspkS8NKxTsYMRejzMLYRA=", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true, + "requires": { + "arr-flatten": "^1.0.1" + } + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-filter": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz", + "integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=", + "dev": true + }, + "array-map": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz", + "integrity": "sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=", + "dev": true + }, + "array-reduce": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/array-reduce/-/array-reduce-0.0.0.tgz", + "integrity": "sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys=", + "dev": true + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "assert": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", + "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", + "dev": true, + "requires": { + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "util": { + "version": "0.10.3", + "resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "requires": { + "inherits": "2.0.1" + } + } + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "async": { + "version": "1.5.2", + "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "async-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", + "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + }, + "babel-cli": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-cli/-/babel-cli-6.26.0.tgz", + "integrity": "sha1-UCq1SHTX24itALiHoGODzgPQAvE=", + "dev": true, + "requires": { + "babel-core": "^6.26.0", + "babel-polyfill": "^6.26.0", + "babel-register": "^6.26.0", + "babel-runtime": "^6.26.0", + "chokidar": "^1.6.1", + "commander": "^2.11.0", + "convert-source-map": "^1.5.0", + "fs-readdir-recursive": "^1.0.0", + "glob": "^7.1.2", + "lodash": "^4.17.4", + "output-file-sync": "^1.1.2", + "path-is-absolute": "^1.0.1", + "slash": "^1.0.0", + "source-map": "^0.5.6", + "v8flags": "^2.1.1" + } + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + } + }, + "babel-core": { + "version": "6.26.3", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", + "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-generator": "^6.26.0", + "babel-helpers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-register": "^6.26.0", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "convert-source-map": "^1.5.1", + "debug": "^2.6.9", + "json5": "^0.5.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.4", + "path-is-absolute": "^1.0.1", + "private": "^0.1.8", + "slash": "^1.0.0", + "source-map": "^0.5.7" + } + }, + "babel-eslint": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-7.2.3.tgz", + "integrity": "sha1-sv4tgBJkcPXBlELcdXJTqJdxCCc=", + "dev": true, + "requires": { + "babel-code-frame": "^6.22.0", + "babel-traverse": "^6.23.1", + "babel-types": "^6.23.0", + "babylon": "^6.17.0" + } + }, + "babel-generator": { + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", + "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", + "dev": true, + "requires": { + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" + } + }, + "babel-helper-call-delegate": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz", + "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", + "dev": true, + "requires": { + "babel-helper-hoist-variables": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-helper-define-map": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz", + "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=", + "dev": true, + "requires": { + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + } + }, + "babel-helper-function-name": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", + "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", + "dev": true, + "requires": { + "babel-helper-get-function-arity": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-helper-get-function-arity": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", + "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-helper-hoist-variables": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz", + "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-helper-optimise-call-expression": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz", + "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-helper-regex": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz", + "integrity": "sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + } + }, + "babel-helper-replace-supers": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", + "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", + "dev": true, + "requires": { + "babel-helper-optimise-call-expression": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-helpers": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", + "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-check-es2015-constants": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", + "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-syntax-async-functions": { + "version": "6.13.0", + "resolved": "http://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", + "integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=", + "dev": true + }, + "babel-plugin-transform-async-to-bluebird": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-to-bluebird/-/babel-plugin-transform-async-to-bluebird-1.1.1.tgz", + "integrity": "sha1-Ruo+fFr2KXgqyfHtG3zTj4Qlr9Q=", + "dev": true, + "requires": { + "babel-helper-function-name": "^6.8.0", + "babel-plugin-syntax-async-functions": "^6.8.0", + "babel-template": "^6.9.0", + "babel-traverse": "^6.10.4" + } + }, + "babel-plugin-transform-es2015-arrow-functions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", + "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-block-scoped-functions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz", + "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-block-scoping": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz", + "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + } + }, + "babel-plugin-transform-es2015-classes": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz", + "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", + "dev": true, + "requires": { + "babel-helper-define-map": "^6.24.1", + "babel-helper-function-name": "^6.24.1", + "babel-helper-optimise-call-expression": "^6.24.1", + "babel-helper-replace-supers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-computed-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz", + "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-destructuring": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz", + "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-duplicate-keys": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz", + "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-for-of": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz", + "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-function-name": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz", + "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", + "dev": true, + "requires": { + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-literals": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz", + "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-modules-amd": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz", + "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=", + "dev": true, + "requires": { + "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-modules-commonjs": { + "version": "6.26.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz", + "integrity": "sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==", + "dev": true, + "requires": { + "babel-plugin-transform-strict-mode": "^6.24.1", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-types": "^6.26.0" + } + }, + "babel-plugin-transform-es2015-modules-systemjs": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz", + "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=", + "dev": true, + "requires": { + "babel-helper-hoist-variables": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-modules-umd": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz", + "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=", + "dev": true, + "requires": { + "babel-plugin-transform-es2015-modules-amd": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-object-super": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz", + "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", + "dev": true, + "requires": { + "babel-helper-replace-supers": "^6.24.1", + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-parameters": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz", + "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", + "dev": true, + "requires": { + "babel-helper-call-delegate": "^6.24.1", + "babel-helper-get-function-arity": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-shorthand-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz", + "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-spread": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz", + "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-sticky-regex": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz", + "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=", + "dev": true, + "requires": { + "babel-helper-regex": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-template-literals": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz", + "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-typeof-symbol": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz", + "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-unicode-regex": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz", + "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=", + "dev": true, + "requires": { + "babel-helper-regex": "^6.24.1", + "babel-runtime": "^6.22.0", + "regexpu-core": "^2.0.0" + } + }, + "babel-plugin-transform-regenerator": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz", + "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=", + "dev": true, + "requires": { + "regenerator-transform": "^0.10.0" + } + }, + "babel-plugin-transform-runtime": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-runtime/-/babel-plugin-transform-runtime-6.23.0.tgz", + "integrity": "sha1-iEkNRGUC6puOfvsP4J7E2ZR5se4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-strict-mode": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", + "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-polyfill": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", + "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "regenerator-runtime": "^0.10.5" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", + "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=", + "dev": true + } + } + }, + "babel-preset-es2015": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz", + "integrity": "sha1-1EBQ1rwsn+6nAqrzjXJ6AhBTiTk=", + "dev": true, + "requires": { + "babel-plugin-check-es2015-constants": "^6.22.0", + "babel-plugin-transform-es2015-arrow-functions": "^6.22.0", + "babel-plugin-transform-es2015-block-scoped-functions": "^6.22.0", + "babel-plugin-transform-es2015-block-scoping": "^6.24.1", + "babel-plugin-transform-es2015-classes": "^6.24.1", + "babel-plugin-transform-es2015-computed-properties": "^6.24.1", + "babel-plugin-transform-es2015-destructuring": "^6.22.0", + "babel-plugin-transform-es2015-duplicate-keys": "^6.24.1", + "babel-plugin-transform-es2015-for-of": "^6.22.0", + "babel-plugin-transform-es2015-function-name": "^6.24.1", + "babel-plugin-transform-es2015-literals": "^6.22.0", + "babel-plugin-transform-es2015-modules-amd": "^6.24.1", + "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", + "babel-plugin-transform-es2015-modules-systemjs": "^6.24.1", + "babel-plugin-transform-es2015-modules-umd": "^6.24.1", + "babel-plugin-transform-es2015-object-super": "^6.24.1", + "babel-plugin-transform-es2015-parameters": "^6.24.1", + "babel-plugin-transform-es2015-shorthand-properties": "^6.24.1", + "babel-plugin-transform-es2015-spread": "^6.22.0", + "babel-plugin-transform-es2015-sticky-regex": "^6.24.1", + "babel-plugin-transform-es2015-template-literals": "^6.22.0", + "babel-plugin-transform-es2015-typeof-symbol": "^6.22.0", + "babel-plugin-transform-es2015-unicode-regex": "^6.24.1", + "babel-plugin-transform-regenerator": "^6.24.1" + } + }, + "babel-register": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", + "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", + "dev": true, + "requires": { + "babel-core": "^6.26.0", + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "home-or-tmp": "^2.0.0", + "lodash": "^4.17.4", + "mkdirp": "^0.5.1", + "source-map-support": "^0.4.15" + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-template": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" + } + }, + "babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "base-x": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.4.tgz", + "integrity": "sha512-UYOadoSIkEI/VrRGSG6qp93rp2WdokiAiNYDfGW5qURAY8GiAQkvMbwNNSDYiVJopqv4gCna7xqf4rrNGp+5AA==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "base64-js": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "binary-extensions": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.12.0.tgz", + "integrity": "sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg==", + "dev": true + }, + "bluebird": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz", + "integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==" + }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dev": true, + "requires": { + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "browser-pack": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.1.0.tgz", + "integrity": "sha512-erYug8XoqzU3IfcU8fUgyHqyOXqIE4tUTTQ+7mqUjQlvnXkOO6OlT9c/ZoJVHYoAaqGxr09CN53G7XIsO4KtWA==", + "dev": true, + "requires": { + "JSONStream": "^1.0.3", + "combine-source-map": "~0.8.0", + "defined": "^1.0.0", + "safe-buffer": "^5.1.1", + "through2": "^2.0.0", + "umd": "^3.0.0" + } + }, + "browser-request": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/browser-request/-/browser-request-0.3.3.tgz", + "integrity": "sha1-ns5bWsqJopkyJC4Yv5M975h2zBc=" + }, + "browser-resolve": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", + "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", + "dev": true, + "requires": { + "resolve": "1.1.7" + }, + "dependencies": { + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + } + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "browserify": { + "version": "14.5.0", + "resolved": "https://registry.npmjs.org/browserify/-/browserify-14.5.0.tgz", + "integrity": "sha512-gKfOsNQv/toWz+60nSPfYzuwSEdzvV2WdxrVPUbPD/qui44rAkB3t3muNtmmGYHqrG56FGwX9SUEQmzNLAeS7g==", + "dev": true, + "requires": { + "JSONStream": "^1.0.3", + "assert": "^1.4.0", + "browser-pack": "^6.0.1", + "browser-resolve": "^1.11.0", + "browserify-zlib": "~0.2.0", + "buffer": "^5.0.2", + "cached-path-relative": "^1.0.0", + "concat-stream": "~1.5.1", + "console-browserify": "^1.1.0", + "constants-browserify": "~1.0.0", + "crypto-browserify": "^3.0.0", + "defined": "^1.0.0", + "deps-sort": "^2.0.0", + "domain-browser": "~1.1.0", + "duplexer2": "~0.1.2", + "events": "~1.1.0", + "glob": "^7.1.0", + "has": "^1.0.0", + "htmlescape": "^1.1.0", + "https-browserify": "^1.0.0", + "inherits": "~2.0.1", + "insert-module-globals": "^7.0.0", + "labeled-stream-splicer": "^2.0.0", + "module-deps": "^4.0.8", + "os-browserify": "~0.3.0", + "parents": "^1.0.1", + "path-browserify": "~0.0.0", + "process": "~0.11.0", + "punycode": "^1.3.2", + "querystring-es3": "~0.2.0", + "read-only-stream": "^2.0.0", + "readable-stream": "^2.0.2", + "resolve": "^1.1.4", + "shasum": "^1.0.0", + "shell-quote": "^1.6.1", + "stream-browserify": "^2.0.0", + "stream-http": "^2.0.0", + "string_decoder": "~1.0.0", + "subarg": "^1.0.0", + "syntax-error": "^1.1.1", + "through2": "^2.0.0", + "timers-browserify": "^1.0.1", + "tty-browserify": "~0.0.0", + "url": "~0.11.0", + "util": "~0.10.1", + "vm-browserify": "~0.0.1", + "xtend": "^4.0.0" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.0.1", + "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" + } + }, + "browserify-shim": { + "version": "3.8.14", + "resolved": "https://registry.npmjs.org/browserify-shim/-/browserify-shim-3.8.14.tgz", + "integrity": "sha1-vxBXAmky0yU8de991xTzuHft7Gs=", + "dev": true, + "requires": { + "exposify": "~0.5.0", + "mothership": "~0.2.0", + "rename-function-calls": "~0.1.0", + "resolve": "~0.6.1", + "through": "~2.3.4" + }, + "dependencies": { + "resolve": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-0.6.3.tgz", + "integrity": "sha1-3ZV5gufnNt699TtYpN2RdUV13UY=", + "dev": true + } + } + }, + "browserify-sign": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", + "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "dev": true, + "requires": { + "bn.js": "^4.1.1", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.2", + "elliptic": "^6.0.0", + "inherits": "^2.0.1", + "parse-asn1": "^5.0.0" + } + }, + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "requires": { + "pako": "~1.0.5" + } + }, + "bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", + "requires": { + "base-x": "^3.0.2" + } + }, + "buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz", + "integrity": "sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==", + "dev": true, + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "cached-path-relative": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.0.2.tgz", + "integrity": "sha512-5r2GqsoEb4qMTTN9J+WzXfjov+hjxT+j3u5K+kIVNIwAd99DLCJE9pBIMP1qVeybV6JiijL385Oz0DcYxfbOIg==", + "dev": true + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "^0.2.0" + } + }, + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + }, + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "catharsis": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.8.9.tgz", + "integrity": "sha1-mMyJDKZS3S7w5ws3klMQ/56Q/Is=", + "dev": true, + "requires": { + "underscore-contrib": "~0.3.0" + } + }, + "center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "dev": true, + "requires": { + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + } + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "chokidar": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", + "dev": true, + "requires": { + "anymatch": "^1.3.0", + "async-each": "^1.0.0", + "fsevents": "^1.0.0", + "glob-parent": "^2.0.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^2.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0" + } + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "cli-cursor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "dev": true, + "requires": { + "restore-cursor": "^1.0.1" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "dev": true, + "requires": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true + } + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "combine-source-map": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz", + "integrity": "sha1-pY0N8ELBhvz4IqjoAV9UUNLXmos=", + "dev": true, + "requires": { + "convert-source-map": "~1.1.0", + "inline-source-map": "~0.6.0", + "lodash.memoize": "~3.0.3", + "source-map": "~0.5.3" + }, + "dependencies": { + "convert-source-map": { + "version": "1.1.3", + "resolved": "http://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", + "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=", + "dev": true + } + } + }, + "combined-stream": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", + "dev": true + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz", + "integrity": "sha1-cIl4Yk2FavQaWnQd790mHadSwmY=", + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "~2.0.0", + "typedarray": "~0.0.5" + }, + "dependencies": { + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "readable-stream": { + "version": "2.0.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "string_decoder": "~0.10.x", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "console-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "dev": true, + "requires": { + "date-now": "^0.1.4" + } + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "core-js": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", + "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "create-ecdh": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", + "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.0.0" + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "d": { + "version": "1.0.0", + "resolved": "http://registry.npmjs.org/d/-/d-1.0.0.tgz", + "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", + "dev": true, + "requires": { + "es5-ext": "^0.10.9" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "date-now": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + }, + "dependencies": { + "object-keys": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", + "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==", + "dev": true + } + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", + "dev": true + }, + "del": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", + "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", + "dev": true, + "requires": { + "globby": "^6.1.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "p-map": "^1.1.1", + "pify": "^3.0.0", + "rimraf": "^2.2.8" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "deps-sort": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/deps-sort/-/deps-sort-2.0.0.tgz", + "integrity": "sha1-CRckkC6EZYJg65EHSMzNGvbiH7U=", + "dev": true, + "requires": { + "JSONStream": "^1.0.3", + "shasum": "^1.0.0", + "subarg": "^1.0.0", + "through2": "^2.0.0" + } + }, + "des.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", + "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "detect-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "detective": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/detective/-/detective-4.7.1.tgz", + "integrity": "sha512-H6PmeeUcZloWtdt4DAkFyzFL94arpHr3NOwwmVILFiy+9Qd4JTxxXrzfyGk/lmct2qVGBwTSwSXagqu2BxmWig==", + "dev": true, + "requires": { + "acorn": "^5.2.1", + "defined": "^1.0.0" + }, + "dependencies": { + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "dev": true + } + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "domain-browser": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz", + "integrity": "sha1-hnqksJP6oF8d4IwG9NeyH9+GmLw=", + "dev": true + }, + "dot-parts": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dot-parts/-/dot-parts-1.0.1.tgz", + "integrity": "sha1-iEvXvPwwgv+tL+XbU+SU2PPgdD8=", + "dev": true + }, + "duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", + "dev": true, + "requires": { + "readable-stream": "^2.0.2" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "elliptic": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz", + "integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==", + "dev": true, + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, + "es-abstract": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz", + "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", + "dev": true, + "requires": { + "es-to-primitive": "^1.1.1", + "function-bind": "^1.1.1", + "has": "^1.0.1", + "is-callable": "^1.1.3", + "is-regex": "^1.0.4" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es5-ext": { + "version": "0.10.46", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.46.tgz", + "integrity": "sha512-24XxRvJXNFwEMpJb3nOkiRJKRoupmjYmOPVlI65Qy2SrtxwOTB+g6ODjBKOtwEHbYrhWRty9xxOWLNdClT2djw==", + "dev": true, + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.1", + "next-tick": "1" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-map": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", + "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-set": "~0.1.5", + "es6-symbol": "~3.1.1", + "event-emitter": "~0.3.5" + } + }, + "es6-set": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", + "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-symbol": "3.1.1", + "event-emitter": "~0.3.5" + } + }, + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "es6-weak-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", + "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.14", + "es6-iterator": "^2.0.1", + "es6-symbol": "^3.1.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "escodegen": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.1.0.tgz", + "integrity": "sha1-xmOSP24gqtSNDA+knzHG1PSTYM8=", + "dev": true, + "requires": { + "esprima": "~1.0.4", + "estraverse": "~1.5.0", + "esutils": "~1.0.0", + "source-map": "~0.1.30" + }, + "dependencies": { + "esprima": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", + "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=", + "dev": true + }, + "esutils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.0.0.tgz", + "integrity": "sha1-gVHTWOIMisx/t0XnRywAJf5JZXA=", + "dev": true + }, + "source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "dev": true, + "optional": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "escope": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", + "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", + "dev": true, + "requires": { + "es6-map": "^0.1.3", + "es6-weak-map": "^2.0.1", + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + }, + "dependencies": { + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + } + } + }, + "eslint": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.19.0.tgz", + "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=", + "dev": true, + "requires": { + "babel-code-frame": "^6.16.0", + "chalk": "^1.1.3", + "concat-stream": "^1.5.2", + "debug": "^2.1.1", + "doctrine": "^2.0.0", + "escope": "^3.6.0", + "espree": "^3.4.0", + "esquery": "^1.0.0", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "glob": "^7.0.3", + "globals": "^9.14.0", + "ignore": "^3.2.0", + "imurmurhash": "^0.1.4", + "inquirer": "^0.12.0", + "is-my-json-valid": "^2.10.0", + "is-resolvable": "^1.0.0", + "js-yaml": "^3.5.1", + "json-stable-stringify": "^1.0.0", + "levn": "^0.3.0", + "lodash": "^4.0.0", + "mkdirp": "^0.5.0", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.1", + "pluralize": "^1.2.1", + "progress": "^1.1.8", + "require-uncached": "^1.0.2", + "shelljs": "^0.7.5", + "strip-bom": "^3.0.0", + "strip-json-comments": "~2.0.1", + "table": "^3.7.8", + "text-table": "~0.2.0", + "user-home": "^2.0.0" + }, + "dependencies": { + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dev": true, + "requires": { + "jsonify": "~0.0.0" + } + }, + "user-home": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", + "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", + "dev": true, + "requires": { + "os-homedir": "^1.0.0" + } + } + } + }, + "eslint-config-google": { + "version": "0.7.1", + "resolved": "http://registry.npmjs.org/eslint-config-google/-/eslint-config-google-0.7.1.tgz", + "integrity": "sha1-VZj4SY6eB4Qg80uASVuNlZ9lH7I=", + "dev": true + }, + "espree": { + "version": "3.5.4", + "resolved": "http://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "dev": true, + "requires": { + "acorn": "^5.5.0", + "acorn-jsx": "^3.0.0" + }, + "dependencies": { + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "dev": true + } + } + }, + "esprima-fb": { + "version": "3001.1.0-dev-harmony-fb", + "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-3001.0001.0000-dev-harmony-fb.tgz", + "integrity": "sha1-t303q8046gt3Qmu4vCkizmtCZBE=", + "dev": true + }, + "esquery": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "dev": true, + "requires": { + "estraverse": "^4.0.0" + }, + "dependencies": { + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + }, + "dependencies": { + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + } + } + }, + "estraverse": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.5.1.tgz", + "integrity": "sha1-hno+jlip+EYYr7bC3bzZFrfLr3E=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "events": { + "version": "1.1.1", + "resolved": "http://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", + "dev": true + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "exit-hook": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", + "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", + "dev": true + }, + "exorcist": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/exorcist/-/exorcist-0.4.0.tgz", + "integrity": "sha1-EjD/3t2SSPQvvM+LSkTUyrKePGQ=", + "dev": true, + "requires": { + "minimist": "0.0.5", + "mold-source-map": "~0.4.0", + "nave": "~0.5.1" + }, + "dependencies": { + "minimist": { + "version": "0.0.5", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.5.tgz", + "integrity": "sha1-16oye87PUY+RBqxrjwA/o7zqhWY=", + "dev": true + } + } + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "dev": true, + "requires": { + "is-posix-bracket": "^0.1.0" + } + }, + "expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "dev": true, + "requires": { + "fill-range": "^2.1.0" + } + }, + "expect": { + "version": "1.20.2", + "resolved": "http://registry.npmjs.org/expect/-/expect-1.20.2.tgz", + "integrity": "sha1-1Fj+TFYAQDa64yMkFqP2Nh8E+WU=", + "dev": true, + "requires": { + "define-properties": "~1.1.2", + "has": "^1.0.1", + "is-equal": "^1.5.1", + "is-regex": "^1.0.3", + "object-inspect": "^1.1.0", + "object-keys": "^1.0.9", + "tmatch": "^2.0.1" + }, + "dependencies": { + "object-keys": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", + "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==", + "dev": true + } + } + }, + "exposify": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/exposify/-/exposify-0.5.0.tgz", + "integrity": "sha1-+S0AlMJls/VT4fpFagOhiD0QWcw=", + "dev": true, + "requires": { + "globo": "~1.1.0", + "map-obj": "~1.0.1", + "replace-requires": "~1.0.3", + "through2": "~0.4.0", + "transformify": "~0.1.1" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "through2": { + "version": "0.4.2", + "resolved": "http://registry.npmjs.org/through2/-/through2-0.4.2.tgz", + "integrity": "sha1-2/WGYDEVHsg1K7bE22SiKSqEC5s=", + "dev": true, + "requires": { + "readable-stream": "~1.0.17", + "xtend": "~2.1.1" + } + }, + "xtend": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", + "dev": true, + "requires": { + "object-keys": "~0.4.0" + } + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" + } + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "requires": { + "flat-cache": "^1.2.1", + "object-assign": "^4.0.1" + } + }, + "filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", + "dev": true + }, + "fill-range": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", + "dev": true, + "requires": { + "is-number": "^2.1.0", + "isobject": "^2.0.0", + "randomatic": "^3.0.0", + "repeat-element": "^1.1.2", + "repeat-string": "^1.5.2" + } + }, + "find-parent-dir": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/find-parent-dir/-/find-parent-dir-0.3.0.tgz", + "integrity": "sha1-M8RLQpqysvBkYpnF+fcY83b/jVQ=", + "dev": true + }, + "flat-cache": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.2.tgz", + "integrity": "sha512-KByBY8c98sLUAGpnmjEdWTrtrLZRtZdwds+kAL/ciFXTCb7AZgqKsAnVnYFQj1hxepwO8JKN/8AsRWwLq+RK0A==", + "dev": true, + "requires": { + "circular-json": "^0.3.1", + "del": "^3.0.0", + "graceful-fs": "^4.1.2", + "write": "^0.2.1" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "dev": true, + "requires": { + "for-in": "^1.0.1" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fs-readdir-recursive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", + "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.9.2", + "node-pre-gyp": "^0.10.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", + "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz", + "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=", + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true, + "optional": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.5.1.tgz", + "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==", + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.21", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.21.tgz", + "integrity": "sha512-En5V9za5mBt2oUA03WGD3TwDv0MKAruqsuxstbMUZaj9W9k/m1CV/9py3l0L5kw9Bln8fdHQmzHSYtvpvTLpKw==", + "dev": true, + "optional": true, + "requires": { + "safer-buffer": "^2.1.0" + } + }, + "ignore-walk": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "minipass": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.2.4.tgz", + "integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.1", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.1.0.tgz", + "integrity": "sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==", + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true, + "optional": true + }, + "needle": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.2.0.tgz", + "integrity": "sha512-eFagy6c+TYayorXw/qtAdSvaUpEbBsDwDyxYFgLZ0lTojfH7K+OdBqAF7TAFwDokJaGpubpSGG0wO3iC0XPi8w==", + "dev": true, + "optional": true, + "requires": { + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.10.0.tgz", + "integrity": "sha512-G7kEonQLRbcA/mOoFoxvlMrw6Q6dPf92+t/l0DFSMuSlDoWaI9JWIyPwK0jyE1bph//CUEL65/Fz1m2vJbmjQQ==", + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.1.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.3.tgz", + "integrity": "sha512-ByQ3oJ/5ETLyglU2+8dBObvhfWXX8dtPZDMePCahptliFX2iIuhyEszyFk401PZUNQH20vvdW5MLjJxkwU80Ow==", + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.1.10.tgz", + "integrity": "sha512-AQC0Dyhzn4EiYEfIUjCdMl0JJ61I2ER9ukf/sLxJUcZHfo+VyEfz2rMJgLZSS1v30OxPQe1cN0LZA1xbcaVfWA==", + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.7.tgz", + "integrity": "sha512-LdLD8xD4zzLsAT5xyushXDNscEjB7+2ulnl8+r1pnESlYtlJtVSoCMBGr30eDRJ3+2Gq89jK9P9e4tCEH1+ywA==", + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.5.1", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, + "optional": true, + "requires": { + "glob": "^7.0.5" + } + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true, + "optional": true + }, + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.1.tgz", + "integrity": "sha512-O+v1r9yN4tOsvl90p5HAP4AEqbYhx4036AGMm075fH9F8Qwi3oJ+v4u50FkT/KkvywNGtwkk0zRI+8eYm1X/xg==", + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.0.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.2.4", + "minizlib": "^1.1.0", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.1", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", + "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "yallist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz", + "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=", + "dev": true + } + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "dev": true, + "requires": { + "is-property": "^1.0.2" + } + }, + "generate-object-property": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", + "dev": true, + "requires": { + "is-property": "^1.0.0" + } + }, + "get-assigned-identifiers": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz", + "integrity": "sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ==", + "dev": true + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "dev": true, + "requires": { + "glob-parent": "^2.0.0", + "is-glob": "^2.0.0" + } + }, + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "dev": true, + "requires": { + "is-glob": "^2.0.0" + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "globo": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/globo/-/globo-1.1.0.tgz", + "integrity": "sha1-DSYJiVXepCLrIAGxBImLChAcqvM=", + "dev": true, + "requires": { + "accessory": "~1.1.0", + "is-defined": "~1.0.0", + "ternary": "~1.0.0" + } + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "handlebars": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.12.tgz", + "integrity": "sha512-RhmTekP+FZL+XNhwS1Wf+bTTZpdLougwt5pcgA1tuz6Jcx0fpH/7z0qd71RKnZHBCxIRBHfBOnio4gViPemNzA==", + "dev": true, + "requires": { + "async": "^2.5.0", + "optimist": "^0.6.1", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" + }, + "dependencies": { + "async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", + "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", + "dev": true, + "requires": { + "lodash": "^4.17.10" + } + }, + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true, + "optional": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "uglify-js": { + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", + "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", + "dev": true, + "optional": true, + "requires": { + "commander": "~2.17.1", + "source-map": "~0.6.1" + } + } + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "has-require": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/has-require/-/has-require-1.2.2.tgz", + "integrity": "sha1-khZ1qxMNvZdo/I2o8ajiQt+kF3Q=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.3" + } + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "hash.js": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.5.tgz", + "integrity": "sha512-eWI5HG9Np+eHV1KQhisXWwM+4EPPYe5dFX1UZZH7k/E3JzDEazVH+VGlZi6R94ZqImq+A3D1mCEtrFIfg/E7sA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "home-or-tmp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", + "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.1" + } + }, + "htmlescape": { + "version": "1.1.1", + "resolved": "http://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz", + "integrity": "sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E=", + "dev": true + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, + "ieee754": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz", + "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==", + "dev": true + }, + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "inline-source-map": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz", + "integrity": "sha1-+Tk0ccGKedFyT4Y/o4tYY3Ct4qU=", + "dev": true, + "requires": { + "source-map": "~0.5.3" + } + }, + "inquirer": { + "version": "0.12.0", + "resolved": "http://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", + "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", + "dev": true, + "requires": { + "ansi-escapes": "^1.1.0", + "ansi-regex": "^2.0.0", + "chalk": "^1.0.0", + "cli-cursor": "^1.0.1", + "cli-width": "^2.0.0", + "figures": "^1.3.5", + "lodash": "^4.3.0", + "readline2": "^1.0.1", + "run-async": "^0.1.0", + "rx-lite": "^3.1.2", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.0", + "through": "^2.3.6" + } + }, + "insert-module-globals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-7.2.0.tgz", + "integrity": "sha512-VE6NlW+WGn2/AeOMd496AHFYmE7eLKkUY6Ty31k4og5vmA3Fjuwe9v6ifH6Xx/Hz27QvdoMoviw1/pqWRB09Sw==", + "dev": true, + "requires": { + "JSONStream": "^1.0.3", + "acorn-node": "^1.5.2", + "combine-source-map": "^0.8.0", + "concat-stream": "^1.6.1", + "is-buffer": "^1.1.0", + "path-is-absolute": "^1.0.1", + "process": "~0.11.0", + "through2": "^2.0.0", + "undeclared-identifiers": "^1.1.2", + "xtend": "^4.0.0" + }, + "dependencies": { + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + } + } + }, + "interpret": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", + "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", + "dev": true + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-arrow-function": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-arrow-function/-/is-arrow-function-2.0.3.tgz", + "integrity": "sha1-Kb4sLY2UUIUri7r7Y1unuNjofsI=", + "dev": true, + "requires": { + "is-callable": "^1.0.4" + } + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-boolean-object": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.0.tgz", + "integrity": "sha1-mPiygDBoQhmpXzdc+9iM40Bd/5M=", + "dev": true + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, + "is-defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-defined/-/is-defined-1.0.0.tgz", + "integrity": "sha1-HwfKZ9Vx9ZTEsUQVpF9774j5K/U=", + "dev": true + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", + "dev": true + }, + "is-equal": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/is-equal/-/is-equal-1.5.5.tgz", + "integrity": "sha1-XoXxlX4FKIMkf+s4aWWju6Ffuz0=", + "dev": true, + "requires": { + "has": "^1.0.1", + "is-arrow-function": "^2.0.3", + "is-boolean-object": "^1.0.0", + "is-callable": "^1.1.3", + "is-date-object": "^1.0.1", + "is-generator-function": "^1.0.6", + "is-number-object": "^1.0.3", + "is-regex": "^1.0.3", + "is-string": "^1.0.4", + "is-symbol": "^1.0.1", + "object.entries": "^1.0.4" + } + }, + "is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "dev": true, + "requires": { + "is-primitive": "^2.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-generator-function": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.7.tgz", + "integrity": "sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } + }, + "is-my-ip-valid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", + "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", + "dev": true + }, + "is-my-json-valid": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.19.0.tgz", + "integrity": "sha512-mG0f/unGX1HZ5ep4uhRaPOS8EkAY8/j6mDRMJrutq4CqhoJWYp7qAlonIPy3TV7p3ju4TK9fo/PbnoksWmsp5Q==", + "dev": true, + "requires": { + "generate-function": "^2.0.0", + "generate-object-property": "^1.1.0", + "is-my-ip-valid": "^1.0.0", + "jsonpointer": "^4.0.0", + "xtend": "^4.0.0" + } + }, + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-number-object": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.3.tgz", + "integrity": "sha1-8mWrian0RQNO9q/xWo8AsA9VF5k=", + "dev": true + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "dev": true, + "requires": { + "is-path-inside": "^1.0.0" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", + "dev": true + }, + "is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", + "dev": true + }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", + "dev": true + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, + "is-string": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.4.tgz", + "integrity": "sha1-zDqbaYV9Yh6WNyWiTK7shzuCbmQ=", + "dev": true + }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "istanbul": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", + "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", + "dev": true, + "requires": { + "abbrev": "1.0.x", + "async": "1.x", + "escodegen": "1.8.x", + "esprima": "2.7.x", + "glob": "^5.0.15", + "handlebars": "^4.0.1", + "js-yaml": "3.x", + "mkdirp": "0.5.x", + "nopt": "3.x", + "once": "1.x", + "resolve": "1.1.x", + "supports-color": "^3.1.0", + "which": "^1.1.1", + "wordwrap": "^1.0.0" + }, + "dependencies": { + "escodegen": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", + "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", + "dev": true, + "requires": { + "esprima": "^2.7.1", + "estraverse": "^1.9.1", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.2.0" + } + }, + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true + }, + "estraverse": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", + "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", + "dev": true + }, + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + }, + "source-map": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", + "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", + "dev": true, + "optional": true, + "requires": { + "amdefine": ">=0.0.4" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "js-yaml": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", + "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "dependencies": { + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + } + } + }, + "js2xmlparser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-3.0.0.tgz", + "integrity": "sha1-P7YOqgicVED5MZ9RdgzNB+JJlzM=", + "dev": true, + "requires": { + "xmlcreate": "^1.0.1" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "jsdoc": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.5.5.tgz", + "integrity": "sha512-6PxB65TAU4WO0Wzyr/4/YhlGovXl0EVYfpKbpSroSj0qBxT4/xod/l40Opkm38dRHRdQgdeY836M0uVnJQG7kg==", + "dev": true, + "requires": { + "babylon": "7.0.0-beta.19", + "bluebird": "~3.5.0", + "catharsis": "~0.8.9", + "escape-string-regexp": "~1.0.5", + "js2xmlparser": "~3.0.0", + "klaw": "~2.0.0", + "marked": "~0.3.6", + "mkdirp": "~0.5.1", + "requizzle": "~0.2.1", + "strip-json-comments": "~2.0.1", + "taffydb": "2.6.2", + "underscore": "~1.8.3" + }, + "dependencies": { + "babylon": { + "version": "7.0.0-beta.19", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.19.tgz", + "integrity": "sha512-Vg0C9s/REX6/WIXN37UKpv5ZhRi6A4pjHlpkE34+8/a6c2W1Q692n3hmc+SZG5lKRnaExLUbxtJ1SVT+KaCQ/A==", + "dev": true + } + } + }, + "jsesc": { + "version": "1.3.0", + "resolved": "http://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stable-stringify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz", + "integrity": "sha1-YRwj6BTbN1Un34URk9tZ3Sryf0U=", + "dev": true, + "requires": { + "jsonify": "~0.0.0" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "json5": { + "version": "0.5.1", + "resolved": "http://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true + }, + "jsonpointer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", + "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, + "klaw": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-2.0.0.tgz", + "integrity": "sha1-WcEo4Nxc5BAgEVEZTuucv4WGUPY=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.9" + } + }, + "labeled-stream-splicer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.1.tgz", + "integrity": "sha512-MC94mHZRvJ3LfykJlTUipBqenZz1pacOZEMhhQ8dMGcDHs0SBE5GbsavUXV7YtP3icBW17W0Zy1I0lfASmo9Pg==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "isarray": "^2.0.4", + "stream-splicer": "^2.0.0" + }, + "dependencies": { + "isarray": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.4.tgz", + "integrity": "sha512-GMxXOiUirWg1xTKRipM0Ek07rX+ubx4nNVElTJdNLYmNO/2YrDkgJGw9CljXn+r4EWiDQg/8lsRdHyg2PJuUaA==", + "dev": true + } + } + }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "lodash.memoize": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", + "integrity": "sha1-LcvSwofLwKVcxCMovQxzYVDVPj8=", + "dev": true + }, + "loglevel": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz", + "integrity": "sha1-4PyVEztu8nbNyIh82vJKpvFW+Po=" + }, + "lolex": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.6.0.tgz", + "integrity": "sha1-OpoCg0UqR9dDnnJzG54H1zhuSfY=", + "dev": true + }, + "longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "marked": { + "version": "0.3.19", + "resolved": "http://registry.npmjs.org/marked/-/marked-0.3.19.tgz", + "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", + "dev": true + }, + "math-random": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.1.tgz", + "integrity": "sha1-izqsWIuKZuSXXjzepn97sylgH6w=", + "dev": true + }, + "matrix-mock-request": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/matrix-mock-request/-/matrix-mock-request-1.2.2.tgz", + "integrity": "sha512-9u86m6rOsKekNkqUUkStWXNULrY9G+9ibwolfrmgqTmgR76EGCr9fovM+JPWn4U+TbrewvHMALpPw8OxRg0ExA==", + "dev": true, + "requires": { + "bluebird": "^3.5.0", + "expect": "^1.20.2" + } + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "dev": true, + "requires": { + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" + } + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + } + }, + "mime-db": { + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" + }, + "mime-types": { + "version": "2.1.21", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", + "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", + "requires": { + "mime-db": "~1.37.0" + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mixin-deep": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", + "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "dev": true, + "requires": { + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "5.4.0" + }, + "dependencies": { + "commander": { + "version": "2.15.1", + "resolved": "http://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "mocha-jenkins-reporter": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/mocha-jenkins-reporter/-/mocha-jenkins-reporter-0.4.1.tgz", + "integrity": "sha512-IqnIylrkKJG0lxeoawRkhv/uiYojMEw3o9TQOpDFarPYKVq4ymngVPwsyfMB0XMDqtDbOTOCviFg8xOLHb80/Q==", + "dev": true, + "requires": { + "diff": "1.0.7", + "mkdirp": "0.5.1", + "mocha": "^5.2.0", + "xml": "^1.0.1" + }, + "dependencies": { + "diff": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.7.tgz", + "integrity": "sha1-JLuwAcSn1VIhaefKvbLCgU7ZHPQ=", + "dev": true + } + } + }, + "module-deps": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-4.1.1.tgz", + "integrity": "sha1-IyFYM/HaE/1gbMuAh7RIUty4If0=", + "dev": true, + "requires": { + "JSONStream": "^1.0.3", + "browser-resolve": "^1.7.0", + "cached-path-relative": "^1.0.0", + "concat-stream": "~1.5.0", + "defined": "^1.0.0", + "detective": "^4.0.0", + "duplexer2": "^0.1.2", + "inherits": "^2.0.1", + "parents": "^1.0.0", + "readable-stream": "^2.0.2", + "resolve": "^1.1.3", + "stream-combiner2": "^1.1.1", + "subarg": "^1.0.0", + "through2": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "mold-source-map": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/mold-source-map/-/mold-source-map-0.4.0.tgz", + "integrity": "sha1-z2fgsxxHq5uttcnCVlGGISe7gxc=", + "dev": true, + "requires": { + "convert-source-map": "^1.1.0", + "through": "~2.2.7" + }, + "dependencies": { + "through": { + "version": "2.2.7", + "resolved": "http://registry.npmjs.org/through/-/through-2.2.7.tgz", + "integrity": "sha1-bo4hIAGR1OtqmfbwEN9Gqhxusr0=", + "dev": true + } + } + }, + "mothership": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/mothership/-/mothership-0.2.0.tgz", + "integrity": "sha1-k9SKL7w+UOKl/I7VhvW8RMZfmpk=", + "dev": true, + "requires": { + "find-parent-dir": "~0.3.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "mute-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", + "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", + "dev": true + }, + "nan": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.1.tgz", + "integrity": "sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA==", + "dev": true, + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "nave": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/nave/-/nave-0.5.3.tgz", + "integrity": "sha1-Ws7HI3WFblx2yDvSGmjXE+tfG6Q=", + "dev": true + }, + "next-tick": { + "version": "1.0.0", + "resolved": "http://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "object-inspect": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", + "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", + "dev": true + }, + "object-keys": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "object.entries": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.0.4.tgz", + "integrity": "sha1-G/mk3SKI9bM/Opk9JXZh8F0WGl8=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.6.1", + "function-bind": "^1.1.0", + "has": "^1.0.1" + } + }, + "object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "dev": true, + "requires": { + "for-own": "^0.1.4", + "is-extendable": "^0.1.1" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "dev": true + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + } + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" + } + }, + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", + "dev": true + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "outpipe": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/outpipe/-/outpipe-1.1.1.tgz", + "integrity": "sha1-UM+GFjZeh+Ax4ppeyTOaPaRyX6I=", + "dev": true, + "requires": { + "shell-quote": "^1.4.2" + } + }, + "output-file-sync": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-1.1.2.tgz", + "integrity": "sha1-0KM+7+YaIF+suQCS6CZZjVJFznY=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.4", + "mkdirp": "^0.5.1", + "object-assign": "^4.1.0" + } + }, + "p-map": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", + "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", + "dev": true + }, + "pako": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", + "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==", + "dev": true + }, + "parents": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", + "integrity": "sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E=", + "dev": true, + "requires": { + "path-platform": "~0.11.15" + } + }, + "parse-asn1": { + "version": "5.1.1", + "resolved": "http://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", + "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==", + "dev": true, + "requires": { + "asn1.js": "^4.0.0", + "browserify-aes": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3" + } + }, + "parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", + "dev": true, + "requires": { + "glob-base": "^0.3.0", + "is-dotfile": "^1.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.0" + } + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "patch-text": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/patch-text/-/patch-text-1.0.2.tgz", + "integrity": "sha1-S/NuZeUXM9bpjwz2LgkDTaoDSKw=", + "dev": true + }, + "path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-platform": { + "version": "0.11.15", + "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz", + "integrity": "sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I=", + "dev": true + }, + "pbkdf2": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", + "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "dev": true, + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "pluralize": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", + "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=", + "dev": true + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", + "dev": true + }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "progress": { + "version": "1.1.8", + "resolved": "http://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", + "dev": true + }, + "psl": { + "version": "1.1.29", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", + "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==" + }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true + }, + "randomatic": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", + "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", + "dev": true, + "requires": { + "is-number": "^4.0.0", + "kind-of": "^6.0.0", + "math-random": "^1.0.1" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "randombytes": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.6.tgz", + "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "read-only-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz", + "integrity": "sha1-JyT9aoET1zdkrCiNQ4YnDB2/F/A=", + "dev": true, + "requires": { + "readable-stream": "^2.0.2" + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + } + } + }, + "readline2": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", + "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "mute-stream": "0.0.5" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, + "regenerate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", + "dev": true + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + }, + "regenerator-transform": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz", + "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==", + "dev": true, + "requires": { + "babel-runtime": "^6.18.0", + "babel-types": "^6.19.0", + "private": "^0.1.6" + } + }, + "regex-cache": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "dev": true, + "requires": { + "is-equal-shallow": "^0.1.3" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexpu-core": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", + "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", + "dev": true, + "requires": { + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" + } + }, + "regjsgen": { + "version": "0.2.0", + "resolved": "http://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", + "dev": true + }, + "regjsparser": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "http://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "rename-function-calls": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/rename-function-calls/-/rename-function-calls-0.1.1.tgz", + "integrity": "sha1-f4M2nAB6MAf2q+MDPM+BaGoQjgE=", + "dev": true, + "requires": { + "detective": "~3.1.0" + }, + "dependencies": { + "detective": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detective/-/detective-3.1.0.tgz", + "integrity": "sha1-d3gkRKt1K4jKG+Lp0KA5Xx2iXu0=", + "dev": true, + "requires": { + "escodegen": "~1.1.0", + "esprima-fb": "3001.1.0-dev-harmony-fb" + } + } + } + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "requires": { + "is-finite": "^1.0.0" + } + }, + "replace-requires": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/replace-requires/-/replace-requires-1.0.4.tgz", + "integrity": "sha1-AUtzMLa54lV7cQQ7ZvsCZgw79mc=", + "dev": true, + "requires": { + "detective": "^4.5.0", + "has-require": "~1.2.1", + "patch-text": "~1.0.2", + "xtend": "~4.0.0" + } + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "require-uncached": { + "version": "1.0.3", + "resolved": "http://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "requires": { + "caller-path": "^0.1.0", + "resolve-from": "^1.0.0" + } + }, + "requizzle": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.1.tgz", + "integrity": "sha1-aUPDUwxNmn5G8c3dUcFY/GcM294=", + "dev": true, + "requires": { + "underscore": "~1.6.0" + }, + "dependencies": { + "underscore": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", + "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=", + "dev": true + } + } + }, + "resolve": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", + "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", + "dev": true, + "requires": { + "path-parse": "^1.0.5" + } + }, + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "restore-cursor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "dev": true, + "requires": { + "exit-hook": "^1.0.0", + "onetime": "^1.0.0" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "dev": true, + "requires": { + "align-text": "^0.1.1" + } + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, + "requires": { + "glob": "^7.0.5" + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "run-async": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", + "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", + "dev": true, + "requires": { + "once": "^1.3.0" + } + }, + "rx-lite": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", + "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "set-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", + "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "sha.js": { + "version": "2.4.11", + "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shasum": { + "version": "1.0.2", + "resolved": "http://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz", + "integrity": "sha1-5wEjENj0F/TetXEhUOVni4euVl8=", + "dev": true, + "requires": { + "json-stable-stringify": "~0.0.0", + "sha.js": "~2.4.4" + } + }, + "shell-quote": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz", + "integrity": "sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c=", + "dev": true, + "requires": { + "array-filter": "~0.0.0", + "array-map": "~0.0.0", + "array-reduce": "~0.0.0", + "jsonify": "~0.0.0" + } + }, + "shelljs": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", + "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", + "dev": true, + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, + "simple-concat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz", + "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=", + "dev": true + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true + }, + "slice-ansi": { + "version": "0.0.4", + "resolved": "http://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", + "dev": true + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "dev": true, + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "requires": { + "source-map": "^0.5.6" + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "sourceify": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/sourceify/-/sourceify-0.1.0.tgz", + "integrity": "sha1-C1b+V/lFc1DZJliBCq/afA9EA0w=", + "dev": true, + "requires": { + "convert-source-map": "^1.1.3", + "through2": "^2.0.0" + } + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz", + "integrity": "sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "stream-browserify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", + "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "stream-combiner2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", + "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", + "dev": true, + "requires": { + "duplexer2": "~0.1.0", + "readable-stream": "^2.0.2" + } + }, + "stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "dev": true, + "requires": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + } + }, + "stream-splicer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-2.0.0.tgz", + "integrity": "sha1-G2O+Q4oTPktnHMGTUZdgAXWRDYM=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.2" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "subarg": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", + "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=", + "dev": true, + "requires": { + "minimist": "^1.1.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "syntax-error": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz", + "integrity": "sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==", + "dev": true, + "requires": { + "acorn-node": "^1.2.0" + } + }, + "table": { + "version": "3.8.3", + "resolved": "http://registry.npmjs.org/table/-/table-3.8.3.tgz", + "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", + "dev": true, + "requires": { + "ajv": "^4.7.0", + "ajv-keywords": "^1.0.0", + "chalk": "^1.1.1", + "lodash": "^4.0.0", + "slice-ansi": "0.0.4", + "string-width": "^2.0.0" + }, + "dependencies": { + "ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "dev": true, + "requires": { + "co": "^4.6.0", + "json-stable-stringify": "^1.0.1" + } + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dev": true, + "requires": { + "jsonify": "~0.0.0" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "taffydb": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", + "integrity": "sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=", + "dev": true + }, + "ternary": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ternary/-/ternary-1.0.0.tgz", + "integrity": "sha1-RXAnJWCMlJnUapYQ6bDkn/JveJ4=", + "dev": true + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "timers-browserify": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz", + "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=", + "dev": true, + "requires": { + "process": "~0.11.0" + } + }, + "tmatch": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tmatch/-/tmatch-2.0.1.tgz", + "integrity": "sha1-DFYkbzPzDaG409colauvFmYPOM8=", + "dev": true + }, + "to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", + "dev": true + }, + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + } + } + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + } + } + }, + "transformify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/transformify/-/transformify-0.1.2.tgz", + "integrity": "sha1-mk9CoVRDPdcnuAV1Qoo8nlSJ6/E=", + "dev": true, + "requires": { + "readable-stream": "~1.1.9" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, + "tty-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", + "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "dev": true, + "requires": { + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "dev": true, + "optional": true + }, + "umd": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.3.tgz", + "integrity": "sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==", + "dev": true + }, + "undeclared-identifiers": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/undeclared-identifiers/-/undeclared-identifiers-1.1.2.tgz", + "integrity": "sha512-13EaeocO4edF/3JKime9rD7oB6QI8llAGhgn5fKOPyfkJbRb6NFv9pYV6dFEmpa4uRjKeBqLZP8GpuzqHlKDMQ==", + "dev": true, + "requires": { + "acorn-node": "^1.3.0", + "get-assigned-identifiers": "^1.2.0", + "simple-concat": "^1.0.0", + "xtend": "^4.0.1" + } + }, + "underscore": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=", + "dev": true + }, + "underscore-contrib": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/underscore-contrib/-/underscore-contrib-0.3.0.tgz", + "integrity": "sha1-ZltmwkeD+PorGMn4y7Dix9SMJsc=", + "dev": true, + "requires": { + "underscore": "1.6.0" + }, + "dependencies": { + "underscore": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", + "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=", + "dev": true + } + } + }, + "union-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", + "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^0.4.3" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "set-value": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", + "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.1", + "to-object-path": "^0.3.0" + } + } + } + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "user-home": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", + "integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=", + "dev": true + }, + "util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "dev": true, + "requires": { + "inherits": "2.0.3" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + }, + "v8flags": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-2.1.1.tgz", + "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=", + "dev": true, + "requires": { + "user-home": "^1.1.1" + } + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "vm-browserify": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", + "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", + "dev": true, + "requires": { + "indexof": "0.0.1" + } + }, + "watchify": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/watchify/-/watchify-3.11.0.tgz", + "integrity": "sha512-7jWG0c3cKKm2hKScnSAMUEUjRJKXUShwMPk0ASVhICycQhwND3IMAdhJYmc1mxxKzBUJTSF5HZizfrKrS6BzkA==", + "dev": true, + "requires": { + "anymatch": "^1.3.0", + "browserify": "^16.1.0", + "chokidar": "^1.0.0", + "defined": "^1.0.0", + "outpipe": "^1.1.0", + "through2": "^2.0.0", + "xtend": "^4.0.0" + }, + "dependencies": { + "browserify": { + "version": "16.2.3", + "resolved": "https://registry.npmjs.org/browserify/-/browserify-16.2.3.tgz", + "integrity": "sha512-zQt/Gd1+W+IY+h/xX2NYMW4orQWhqSwyV+xsblycTtpOuB27h1fZhhNQuipJ4t79ohw4P4mMem0jp/ZkISQtjQ==", + "dev": true, + "requires": { + "JSONStream": "^1.0.3", + "assert": "^1.4.0", + "browser-pack": "^6.0.1", + "browser-resolve": "^1.11.0", + "browserify-zlib": "~0.2.0", + "buffer": "^5.0.2", + "cached-path-relative": "^1.0.0", + "concat-stream": "^1.6.0", + "console-browserify": "^1.1.0", + "constants-browserify": "~1.0.0", + "crypto-browserify": "^3.0.0", + "defined": "^1.0.0", + "deps-sort": "^2.0.0", + "domain-browser": "^1.2.0", + "duplexer2": "~0.1.2", + "events": "^2.0.0", + "glob": "^7.1.0", + "has": "^1.0.0", + "htmlescape": "^1.1.0", + "https-browserify": "^1.0.0", + "inherits": "~2.0.1", + "insert-module-globals": "^7.0.0", + "labeled-stream-splicer": "^2.0.0", + "mkdirp": "^0.5.0", + "module-deps": "^6.0.0", + "os-browserify": "~0.3.0", + "parents": "^1.0.1", + "path-browserify": "~0.0.0", + "process": "~0.11.0", + "punycode": "^1.3.2", + "querystring-es3": "~0.2.0", + "read-only-stream": "^2.0.0", + "readable-stream": "^2.0.2", + "resolve": "^1.1.4", + "shasum": "^1.0.0", + "shell-quote": "^1.6.1", + "stream-browserify": "^2.0.0", + "stream-http": "^2.0.0", + "string_decoder": "^1.1.1", + "subarg": "^1.0.0", + "syntax-error": "^1.1.1", + "through2": "^2.0.0", + "timers-browserify": "^1.0.1", + "tty-browserify": "0.0.1", + "url": "~0.11.0", + "util": "~0.10.1", + "vm-browserify": "^1.0.0", + "xtend": "^4.0.0" + } + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "detective": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/detective/-/detective-5.1.0.tgz", + "integrity": "sha512-TFHMqfOvxlgrfVzTEkNBSh9SvSNX/HfF4OFI2QFGCyPm02EsyILqnUeb5P6q7JZ3SFNTBL5t2sePRgrN4epUWQ==", + "dev": true, + "requires": { + "acorn-node": "^1.3.0", + "defined": "^1.0.0", + "minimist": "^1.1.1" + } + }, + "domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "dev": true + }, + "events": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/events/-/events-2.1.0.tgz", + "integrity": "sha512-3Zmiobend8P9DjmKAty0Era4jV8oJ0yGYe2nJJAxgymF9+N8F2m0hhZiMoWtcfepExzNKZumFU3ksdQbInGWCg==", + "dev": true + }, + "minimist": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "module-deps": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-6.2.0.tgz", + "integrity": "sha512-hKPmO06so6bL/ZvqVNVqdTVO8UAYsi3tQWlCa+z9KuWhoN4KDQtb5hcqQQv58qYiDE21wIvnttZEPiDgEbpwbA==", + "dev": true, + "requires": { + "JSONStream": "^1.0.3", + "browser-resolve": "^1.7.0", + "cached-path-relative": "^1.0.0", + "concat-stream": "~1.6.0", + "defined": "^1.0.0", + "detective": "^5.0.2", + "duplexer2": "^0.1.2", + "inherits": "^2.0.1", + "parents": "^1.0.0", + "readable-stream": "^2.0.2", + "resolve": "^1.4.0", + "stream-combiner2": "^1.1.1", + "subarg": "^1.0.0", + "through2": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "vm-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.0.tgz", + "integrity": "sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==", + "dev": true + } + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "dev": true + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, + "xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=", + "dev": true + }, + "xmlcreate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-1.0.2.tgz", + "integrity": "sha1-+mv3YqYKQT+z3Y9LA8WyaSONMI8=", + "dev": true + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + }, + "yargs": { + "version": "3.10.0", + "resolved": "http://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dev": true, + "requires": { + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" + } + } + } +} From c53c6a94d72ed936c8af7fe46af0fd465bd602d1 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 15 Nov 2018 16:38:35 +0000 Subject: [PATCH 63/70] Update package-lock so versions are consistent --- package-lock.json | 372 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 320 insertions(+), 52 deletions(-) diff --git a/package-lock.json b/package-lock.json index b56c65f2f22..df95672aa63 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,9 +1,202 @@ { "name": "matrix-js-sdk", - "version": "0.12.0", + "version": "0.13.1", "lockfileVersion": 1, "requires": true, "dependencies": { + "@babel/code-frame": { + "version": "7.0.0-beta.44", + "resolved": "http://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.44.tgz", + "integrity": "sha512-cuAuTTIQ9RqcFRJ/Y8PvTh+paepNcaGxwQwjIDRWPXmzzyAeCO4KqS9ikMvq0MCbRk6GlYKwfzStrcP3/jSL8g==", + "dev": true, + "requires": { + "@babel/highlight": "7.0.0-beta.44" + } + }, + "@babel/generator": { + "version": "7.0.0-beta.44", + "resolved": "http://registry.npmjs.org/@babel/generator/-/generator-7.0.0-beta.44.tgz", + "integrity": "sha512-5xVb7hlhjGcdkKpMXgicAVgx8syK5VJz193k0i/0sLP6DzE6lRrU1K3B/rFefgdo9LPGMAOOOAWW4jycj07ShQ==", + "dev": true, + "requires": { + "@babel/types": "7.0.0-beta.44", + "jsesc": "^2.5.1", + "lodash": "^4.2.0", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + }, + "dependencies": { + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + } + } + }, + "@babel/helper-function-name": { + "version": "7.0.0-beta.44", + "resolved": "http://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.44.tgz", + "integrity": "sha512-MHRG2qZMKMFaBavX0LWpfZ2e+hLloT++N7rfM3DYOMUOGCD8cVjqZpwiL8a0bOX3IYcQev1ruciT0gdFFRTxzg==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "7.0.0-beta.44", + "@babel/template": "7.0.0-beta.44", + "@babel/types": "7.0.0-beta.44" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.0.0-beta.44", + "resolved": "http://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.44.tgz", + "integrity": "sha512-w0YjWVwrM2HwP6/H3sEgrSQdkCaxppqFeJtAnB23pRiJB5E/O9Yp7JAAeWBl+gGEgmBFinnTyOv2RN7rcSmMiw==", + "dev": true, + "requires": { + "@babel/types": "7.0.0-beta.44" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.0.0-beta.44", + "resolved": "http://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-beta.44.tgz", + "integrity": "sha512-aQ7QowtkgKKzPGf0j6u77kBMdUFVBKNHw2p/3HX/POt5/oz8ec5cs0GwlgM8Hz7ui5EwJnzyfRmkNF1Nx1N7aA==", + "dev": true, + "requires": { + "@babel/types": "7.0.0-beta.44" + } + }, + "@babel/highlight": { + "version": "7.0.0-beta.44", + "resolved": "http://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.44.tgz", + "integrity": "sha512-Il19yJvy7vMFm8AVAh6OZzaFoAd0hbkeMZiX3P5HGD+z7dyI7RzndHB0dg6Urh/VAFfHtpOIzDUSxmY6coyZWQ==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/template": { + "version": "7.0.0-beta.44", + "resolved": "http://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.44.tgz", + "integrity": "sha512-w750Sloq0UNifLx1rUqwfbnC6uSUk0mfwwgGRfdLiaUzfAOiH0tHJE6ILQIUi3KYkjiCDTskoIsnfqZvWLBDng==", + "dev": true, + "requires": { + "@babel/code-frame": "7.0.0-beta.44", + "@babel/types": "7.0.0-beta.44", + "babylon": "7.0.0-beta.44", + "lodash": "^4.2.0" + }, + "dependencies": { + "babylon": { + "version": "7.0.0-beta.44", + "resolved": "http://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.44.tgz", + "integrity": "sha512-5Hlm13BJVAioCHpImtFqNOF2H3ieTOHd0fmFGMxOJ9jgeFqeAwsv3u5P5cR7CSeFrkgHsT19DgFJkHV0/Mcd8g==", + "dev": true + } + } + }, + "@babel/traverse": { + "version": "7.0.0-beta.44", + "resolved": "http://registry.npmjs.org/@babel/traverse/-/traverse-7.0.0-beta.44.tgz", + "integrity": "sha512-UHuDz8ukQkJCDASKHf+oDt3FVUzFd+QYfuBIsiNu/4+/ix6pP/C+uQZJ6K1oEfbCMv/IKWbgDEh7fcsnIE5AtA==", + "dev": true, + "requires": { + "@babel/code-frame": "7.0.0-beta.44", + "@babel/generator": "7.0.0-beta.44", + "@babel/helper-function-name": "7.0.0-beta.44", + "@babel/helper-split-export-declaration": "7.0.0-beta.44", + "@babel/types": "7.0.0-beta.44", + "babylon": "7.0.0-beta.44", + "debug": "^3.1.0", + "globals": "^11.1.0", + "invariant": "^2.2.0", + "lodash": "^4.2.0" + }, + "dependencies": { + "babylon": { + "version": "7.0.0-beta.44", + "resolved": "http://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.44.tgz", + "integrity": "sha512-5Hlm13BJVAioCHpImtFqNOF2H3ieTOHd0fmFGMxOJ9jgeFqeAwsv3u5P5cR7CSeFrkgHsT19DgFJkHV0/Mcd8g==", + "dev": true + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "globals": { + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.9.0.tgz", + "integrity": "sha512-5cJVtyXWH8PiJPVLZzzoIizXx944O4OmRro5MWKx5fT4MgcN7OfaMutPeaTdJCCURwbWdhhcCWcKIffPnmTzBg==", + "dev": true + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "@babel/types": { + "version": "7.0.0-beta.44", + "resolved": "http://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.44.tgz", + "integrity": "sha512-5eTV4WRmqbaFM3v9gHAIljEQJU4Ssc6fxL61JN+Oe2ga/BwyjzjamwkCVVAQjHGuAX8i0BWo42dshL8eO5KfLQ==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.2.0", + "to-fast-properties": "^2.0.0" + }, + "dependencies": { + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + } + } + }, "JSONStream": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", @@ -380,15 +573,25 @@ } }, "babel-eslint": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-7.2.3.tgz", - "integrity": "sha1-sv4tgBJkcPXBlELcdXJTqJdxCCc=", + "version": "8.2.6", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-8.2.6.tgz", + "integrity": "sha512-aCdHjhzcILdP8c9lej7hvXKvQieyRt20SF102SIGyY4cUIiw6UaAtK4j2o3dXX74jEmy0TJ0CEhv4fTIM3SzcA==", "dev": true, "requires": { - "babel-code-frame": "^6.22.0", - "babel-traverse": "^6.23.1", - "babel-types": "^6.23.0", - "babylon": "^6.17.0" + "@babel/code-frame": "7.0.0-beta.44", + "@babel/traverse": "7.0.0-beta.44", + "@babel/types": "7.0.0-beta.44", + "babylon": "7.0.0-beta.44", + "eslint-scope": "3.7.1", + "eslint-visitor-keys": "^1.0.0" + }, + "dependencies": { + "babylon": { + "version": "7.0.0-beta.44", + "resolved": "http://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.44.tgz", + "integrity": "sha512-5Hlm13BJVAioCHpImtFqNOF2H3ieTOHd0fmFGMxOJ9jgeFqeAwsv3u5P5cR7CSeFrkgHsT19DgFJkHV0/Mcd8g==", + "dev": true + } } }, "babel-generator": { @@ -1112,9 +1315,9 @@ "dev": true }, "browserify": { - "version": "14.5.0", - "resolved": "https://registry.npmjs.org/browserify/-/browserify-14.5.0.tgz", - "integrity": "sha512-gKfOsNQv/toWz+60nSPfYzuwSEdzvV2WdxrVPUbPD/qui44rAkB3t3muNtmmGYHqrG56FGwX9SUEQmzNLAeS7g==", + "version": "16.2.3", + "resolved": "https://registry.npmjs.org/browserify/-/browserify-16.2.3.tgz", + "integrity": "sha512-zQt/Gd1+W+IY+h/xX2NYMW4orQWhqSwyV+xsblycTtpOuB27h1fZhhNQuipJ4t79ohw4P4mMem0jp/ZkISQtjQ==", "dev": true, "requires": { "JSONStream": "^1.0.3", @@ -1124,15 +1327,15 @@ "browserify-zlib": "~0.2.0", "buffer": "^5.0.2", "cached-path-relative": "^1.0.0", - "concat-stream": "~1.5.1", + "concat-stream": "^1.6.0", "console-browserify": "^1.1.0", "constants-browserify": "~1.0.0", "crypto-browserify": "^3.0.0", "defined": "^1.0.0", "deps-sort": "^2.0.0", - "domain-browser": "~1.1.0", + "domain-browser": "^1.2.0", "duplexer2": "~0.1.2", - "events": "~1.1.0", + "events": "^2.0.0", "glob": "^7.1.0", "has": "^1.0.0", "htmlescape": "^1.1.0", @@ -1140,7 +1343,8 @@ "inherits": "~2.0.1", "insert-module-globals": "^7.0.0", "labeled-stream-splicer": "^2.0.0", - "module-deps": "^4.0.8", + "mkdirp": "^0.5.0", + "module-deps": "^6.0.0", "os-browserify": "~0.3.0", "parents": "^1.0.1", "path-browserify": "~0.0.0", @@ -1154,32 +1358,35 @@ "shell-quote": "^1.6.1", "stream-browserify": "^2.0.0", "stream-http": "^2.0.0", - "string_decoder": "~1.0.0", + "string_decoder": "^1.1.1", "subarg": "^1.0.0", "syntax-error": "^1.1.1", "through2": "^2.0.0", "timers-browserify": "^1.0.1", - "tty-browserify": "~0.0.0", + "tty-browserify": "0.0.1", "url": "~0.11.0", "util": "~0.10.1", - "vm-browserify": "~0.0.1", + "vm-browserify": "^1.0.0", "xtend": "^4.0.0" }, "dependencies": { + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", "dev": true - }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } } } }, @@ -1518,6 +1725,21 @@ "object-visit": "^1.0.0" } }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, "combine-source-map": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz", @@ -1925,9 +2147,9 @@ } }, "domain-browser": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz", - "integrity": "sha1-hnqksJP6oF8d4IwG9NeyH9+GmLw=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", "dev": true }, "dot-parts": { @@ -2201,6 +2423,30 @@ "integrity": "sha1-VZj4SY6eB4Qg80uASVuNlZ9lH7I=", "dev": true }, + "eslint-scope": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", + "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + }, + "dependencies": { + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "dev": true + }, "espree": { "version": "3.5.4", "resolved": "http://registry.npmjs.org/espree/-/espree-3.5.4.tgz", @@ -2282,9 +2528,9 @@ } }, "events": { - "version": "1.1.1", - "resolved": "http://registry.npmjs.org/events/-/events-1.1.1.tgz", - "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/events/-/events-2.1.0.tgz", + "integrity": "sha512-3Zmiobend8P9DjmKAty0Era4jV8oJ0yGYe2nJJAxgymF9+N8F2m0hhZiMoWtcfepExzNKZumFU3ksdQbInGWCg==", "dev": true }, "evp_bytestokey": { @@ -3554,12 +3800,6 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, - "indexof": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", - "dev": true - }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -4527,26 +4767,57 @@ } }, "module-deps": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-4.1.1.tgz", - "integrity": "sha1-IyFYM/HaE/1gbMuAh7RIUty4If0=", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-6.2.0.tgz", + "integrity": "sha512-hKPmO06so6bL/ZvqVNVqdTVO8UAYsi3tQWlCa+z9KuWhoN4KDQtb5hcqQQv58qYiDE21wIvnttZEPiDgEbpwbA==", "dev": true, "requires": { "JSONStream": "^1.0.3", "browser-resolve": "^1.7.0", "cached-path-relative": "^1.0.0", - "concat-stream": "~1.5.0", + "concat-stream": "~1.6.0", "defined": "^1.0.0", - "detective": "^4.0.0", + "detective": "^5.0.2", "duplexer2": "^0.1.2", "inherits": "^2.0.1", "parents": "^1.0.0", "readable-stream": "^2.0.2", - "resolve": "^1.1.3", + "resolve": "^1.4.0", "stream-combiner2": "^1.1.1", "subarg": "^1.0.0", "through2": "^2.0.0", "xtend": "^4.0.0" + }, + "dependencies": { + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "detective": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/detective/-/detective-5.1.0.tgz", + "integrity": "sha512-TFHMqfOvxlgrfVzTEkNBSh9SvSNX/HfF4OFI2QFGCyPm02EsyILqnUeb5P6q7JZ3SFNTBL5t2sePRgrN4epUWQ==", + "dev": true, + "requires": { + "acorn-node": "^1.3.0", + "defined": "^1.0.0", + "minimist": "^1.1.1" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } } }, "mold-source-map": { @@ -6629,13 +6900,10 @@ } }, "vm-browserify": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", - "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", - "dev": true, - "requires": { - "indexof": "0.0.1" - } + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.0.tgz", + "integrity": "sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==", + "dev": true }, "watchify": { "version": "3.11.0", From d99a22d68d687c02ca50c175960fee17b56abf4f Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 16 Nov 2018 14:46:18 +0000 Subject: [PATCH 64/70] Update to new API Also fix test & remove debug logging from test --- spec/unit/crypto.spec.js | 3 +-- src/crypto/index.js | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/spec/unit/crypto.spec.js b/spec/unit/crypto.spec.js index 47d7d2d67bc..e3cabbac604 100644 --- a/spec/unit/crypto.spec.js +++ b/spec/unit/crypto.spec.js @@ -69,6 +69,7 @@ describe("Crypto", function() { mockBaseApis = { sendToDevice: expect.createSpy(), + getKeyBackupVersion: expect.createSpy(), }; mockRoomList = {}; @@ -111,9 +112,7 @@ describe("Crypto", function() { getSender: expect.createSpy().andReturn('@bob:home.server'), }); - console.log("waiting"); await prom; - console.log("done"); }); }); }); diff --git a/src/crypto/index.js b/src/crypto/index.js index 89c152f5b42..bcc5105e74f 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -1459,7 +1459,7 @@ Crypto.prototype._onToDeviceBadEncrypted = async function(event) { // on a current session. // Note that an undecryptable message from another device could easily be spoofed - // is there anything we can do to mitigate this? - const device = this._deviceList.getDeviceByIdentityKey(sender, algorithm, deviceKey); + const device = this._deviceList.getDeviceByIdentityKey(algorithm, deviceKey); const devicesByUser = {}; devicesByUser[sender] = [device]; await olmlib.ensureOlmSessionsForDevices( From 44d99277fe600c23272ec27c8ef4513ce1d977de Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 20 Nov 2018 13:09:59 +0000 Subject: [PATCH 65/70] Support passphrase-based e2e key backups --- src/client.js | 50 +++++++++++++++++---- src/crypto/backup_password.js | 81 +++++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 8 deletions(-) create mode 100644 src/crypto/backup_password.js diff --git a/src/client.js b/src/client.js index 9ff1762c0e7..da7d5218a17 100644 --- a/src/client.js +++ b/src/client.js @@ -49,6 +49,7 @@ import RoomList from './crypto/RoomList'; import Crypto from './crypto'; import { isCryptoAvailable } from './crypto'; import { encodeRecoveryKey, decodeRecoveryKey } from './crypto/recoverykey'; +import { keyForNewBackup, keyForExistingBackup } from './crypto/backup_password'; // Disable warnings for now: we use deprecated bluebird functions // and need to migrate, but they spam the console with warnings. @@ -860,22 +861,37 @@ MatrixClient.prototype.disableKeyBackup = function() { * Set up the data required to create a new backup version. The backup version * will not be created and enabled until createKeyBackupVersion is called. * + * @param {string} password Passphrase string that can be entered by the user + * when restoring the backup as an alternative to entering the recovery key. + * Optional. + * * @returns {object} Object that can be passed to createKeyBackupVersion and * additionally has a 'recovery_key' member with the user-facing recovery key string. */ -MatrixClient.prototype.prepareKeyBackupVersion = function() { +MatrixClient.prototype.prepareKeyBackupVersion = async function(password) { if (this._crypto === null) { throw new Error("End-to-end encryption disabled"); } const decryption = new global.Olm.PkDecryption(); try { - const publicKey = decryption.generate_key(); + let privateKey; + let publicKey; + let authData = {}; + if (password) { + const keyInfo = await keyForNewBackup(password); + publicKey = decryption.init_with_private_key(keyInfo.key); + authData.private_key_salt = keyInfo.salt; + authData.private_key_iterations = keyInfo.iterations; + } else { + publicKey = decryption.generate_key(); + } + + authData.public_key = publicKey; + return { algorithm: olmlib.MEGOLM_BACKUP_ALGORITHM, - auth_data: { - public_key: publicKey, - }, + auth_data: authData, recovery_key: encodeRecoveryKey(decryption.get_private_key()), }; } finally { @@ -992,8 +1008,28 @@ MatrixClient.prototype.isValidRecoveryKey = function(recoveryKey) { } }; -MatrixClient.prototype.restoreKeyBackups = function( +MatrixClient.prototype.restoreKeyBackupWithPassword = async function( + password, targetRoomId, targetSessionId, version, +) { + const backupInfo = await this.getKeyBackupVersion(); + + const privKey = keyForExistingBackup(backupInfo, password); + return this._restoreKeyBackup( + privKey, targetRoomId, targetSessionId, version, + ); +}; + +MatrixClient.prototype.restoreKeyBackupWithRecoveryKey = function( recoveryKey, targetRoomId, targetSessionId, version, +) { + const privKey = decodeRecoveryKey(recoveryKey); + return this._restoreKeyBackup( + privKey, targetRoomId, targetSessionId, version, + ); +}; + +MatrixClient.prototype._restoreKeyBackup = function( + privKey, targetRoomId, targetSessionId, version, ) { if (this._crypto === null) { throw new Error("End-to-end encryption disabled"); @@ -1003,8 +1039,6 @@ MatrixClient.prototype.restoreKeyBackups = function( const path = this._makeKeyBackupPath(targetRoomId, targetSessionId, version); - // FIXME: see the FIXME in createKeyBackupVersion - const privkey = decodeRecoveryKey(recoveryKey); const decryption = new global.Olm.PkDecryption(); try { decryption.init_with_private_key(privkey); diff --git a/src/crypto/backup_password.js b/src/crypto/backup_password.js new file mode 100644 index 00000000000..946b996816b --- /dev/null +++ b/src/crypto/backup_password.js @@ -0,0 +1,81 @@ +/* +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 { randomString } from '../randomstring'; + +const DEFAULT_ITERATIONS = 500000; + +export async function keyForExistingBackup(backupData, password) { + if (!global.Olm) { + throw new Error("Olm is not available"); + } + + const authData = backupData.auth_data; + + 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, backupData.private_key_salt, + backupData.private_key_iterations, + ); +} + +export async function keyForNewBackup(password) { + if (!global.Olm) { + throw new Error("Olm is not available"); + } + + const salt = randomString(32); + + const key = await deriveKey(password, salt, DEFAULT_ITERATIONS); + + return { key, salt, iterations: DEFAULT_ITERATIONS }; +} + +async function deriveKey(password, salt, iterations) { + 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, + global.Olm.PRIVATE_KEY_LENGTH * 8, + ); + + return new Uint8Array(keybits); +} From cb51799246579fa45c3290b7659e417c71b98d4d Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 20 Nov 2018 16:15:29 +0000 Subject: [PATCH 66/70] Make backup restore work --- src/client.js | 4 ++-- src/crypto/backup_password.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/client.js b/src/client.js index da7d5218a17..cd236aedbf3 100644 --- a/src/client.js +++ b/src/client.js @@ -1013,7 +1013,7 @@ MatrixClient.prototype.restoreKeyBackupWithPassword = async function( ) { const backupInfo = await this.getKeyBackupVersion(); - const privKey = keyForExistingBackup(backupInfo, password); + const privKey = await keyForExistingBackup(backupInfo, password); return this._restoreKeyBackup( privKey, targetRoomId, targetSessionId, version, ); @@ -1041,7 +1041,7 @@ MatrixClient.prototype._restoreKeyBackup = function( const decryption = new global.Olm.PkDecryption(); try { - decryption.init_with_private_key(privkey); + decryption.init_with_private_key(privKey); } catch(e) { decryption.free(); throw e; diff --git a/src/crypto/backup_password.js b/src/crypto/backup_password.js index 946b996816b..1a6d1f28426 100644 --- a/src/crypto/backup_password.js +++ b/src/crypto/backup_password.js @@ -33,8 +33,8 @@ export async function keyForExistingBackup(backupData, password) { } return await deriveKey( - password, backupData.private_key_salt, - backupData.private_key_iterations, + password, backupData.auth_data.private_key_salt, + backupData.auth_data.private_key_iterations, ); } From 6047838f53092aa44b3ea3f10a7a7dfdfd353079 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 20 Nov 2018 16:17:58 +0000 Subject: [PATCH 67/70] lint --- src/client.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/client.js b/src/client.js index cd236aedbf3..8ac8b6a1f3a 100644 --- a/src/client.js +++ b/src/client.js @@ -875,9 +875,8 @@ MatrixClient.prototype.prepareKeyBackupVersion = async function(password) { const decryption = new global.Olm.PkDecryption(); try { - let privateKey; let publicKey; - let authData = {}; + const authData = {}; if (password) { const keyInfo = await keyForNewBackup(password); publicKey = decryption.init_with_private_key(keyInfo.key); From eeea70640e6b5c5e769f393e8f50535726e0fa83 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 20 Nov 2018 16:28:29 +0000 Subject: [PATCH 68/70] Add randomString factored out from client secret --- src/client.js | 10 ++-------- src/randomstring.js | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 8 deletions(-) create mode 100644 src/randomstring.js diff --git a/src/client.js b/src/client.js index 8ac8b6a1f3a..daaaf52c97d 100644 --- a/src/client.js +++ b/src/client.js @@ -50,6 +50,7 @@ import Crypto from './crypto'; import { isCryptoAvailable } from './crypto'; import { encodeRecoveryKey, decodeRecoveryKey } from './crypto/recoverykey'; import { keyForNewBackup, keyForExistingBackup } from './crypto/backup_password'; +import { randomString } from './randomstring'; // Disable warnings for now: we use deprecated bluebird functions // and need to migrate, but they spam the console with warnings. @@ -3862,14 +3863,7 @@ MatrixClient.prototype.getEventMapper = function() { * @return {string} A new client secret */ MatrixClient.prototype.generateClientSecret = function() { - let ret = ""; - const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - - for (let i = 0; i < 32; i++) { - ret += chars.charAt(Math.floor(Math.random() * chars.length)); - } - - return ret; + return randomString(32); }; /** */ diff --git a/src/randomstring.js b/src/randomstring.js new file mode 100644 index 00000000000..7ebe4ed78ac --- /dev/null +++ b/src/randomstring.js @@ -0,0 +1,26 @@ +/* +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. +*/ + +export function randomString(len) { + let ret = ""; + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + + for (let i = 0; i < len; ++i) { + ret += chars.charAt(Math.floor(Math.random() * chars.length)); + } + + return ret; +} From abd2ac71688074b3d333c38e590c28c027a1cdb4 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 20 Nov 2018 16:34:04 +0000 Subject: [PATCH 69/70] Rename backup API call in test --- spec/unit/crypto/backup.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/unit/crypto/backup.spec.js b/spec/unit/crypto/backup.spec.js index bf9fe8eddd1..a654312b7db 100644 --- a/spec/unit/crypto/backup.spec.js +++ b/spec/unit/crypto/backup.spec.js @@ -435,7 +435,7 @@ describe("MegolmBackup", function() { client._http.authedRequest = function() { return Promise.resolve(KEY_BACKUP_DATA); }; - return client.restoreKeyBackups( + return client.restoreKeyBackupWithRecoveryKey( "EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d", ROOM_ID, SESSION_ID, @@ -458,7 +458,7 @@ describe("MegolmBackup", function() { }, }); }; - return client.restoreKeyBackups( + return client.restoreKeyBackupWithRecoveryKey( "EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d", ).then(() => { return megolmDecryption.decryptEvent(ENCRYPTED_EVENT); From 092f4217b02f31c17f437895dac48af62a4f2c65 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 21 Nov 2018 17:56:02 +0000 Subject: [PATCH 70/70] docs --- src/client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client.js b/src/client.js index daaaf52c97d..1840b3cac16 100644 --- a/src/client.js +++ b/src/client.js @@ -866,7 +866,7 @@ MatrixClient.prototype.disableKeyBackup = function() { * when restoring the backup as an alternative to entering the recovery key. * Optional. * - * @returns {object} Object that can be passed to createKeyBackupVersion and + * @returns {Promise} Object that can be passed to createKeyBackupVersion and * additionally has a 'recovery_key' member with the user-facing recovery key string. */ MatrixClient.prototype.prepareKeyBackupVersion = async function(password) {