From c8414fdce6ebe303eb2aa023638459747d4c702c Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 12 Jun 2020 13:53:54 +1000 Subject: [PATCH 01/12] Simplify sql and data files --- app/sql.js | 99 +++------------------ js/background.js | 26 +++--- js/models/conversations.js | 13 ++- js/modules/data.d.ts | 21 +---- js/modules/data.js | 34 +++---- js/modules/loki_file_server_api.js | 16 ++-- libloki/storage.js | 15 ---- libtextsecure/account_manager.js | 6 +- ts/session/index.ts | 1 + ts/session/protocols/MultiDeviceProtocol.ts | 93 ++++++++++++++++++- ts/session/types/PubKey.ts | 3 + 11 files changed, 149 insertions(+), 178 deletions(-) diff --git a/app/sql.js b/app/sql.js index 0c2589efa..d23d195d2 100644 --- a/app/sql.js +++ b/app/sql.js @@ -74,12 +74,8 @@ module.exports = { removeAllContactSignedPreKeys, createOrUpdatePairingAuthorisation, - removePairingAuthorisationForSecondaryPubKey, - getAuthorisationForSecondaryPubKey, - getGrantAuthorisationsForPrimaryPubKey, - getSecondaryDevicesFor, - getPrimaryDeviceFor, - getPairedDevicesFor, + getPairingAuthorisationsFor, + removePairingAuthorisationsFor, createOrUpdateItem, getItemById, @@ -1506,42 +1502,19 @@ async function removeAllSignedPreKeys() { const PAIRING_AUTHORISATIONS_TABLE = 'pairingAuthorisations'; -const GUARD_NODE_TABLE = 'guardNodes'; - -async function getAuthorisationForSecondaryPubKey(pubKey, options) { - const granted = options && options.granted; - let filter = ''; - if (granted) { - filter = 'AND isGranted = 1'; - } - const row = await db.get( - `SELECT json FROM ${PAIRING_AUTHORISATIONS_TABLE} WHERE secondaryDevicePubKey = $secondaryDevicePubKey ${filter};`, - { - $secondaryDevicePubKey: pubKey, - } - ); - - if (!row) { - return null; - } - - return jsonToObject(row.json); -} - -async function getGrantAuthorisationsForPrimaryPubKey(primaryDevicePubKey) { +async function getPairingAuthorisationsFor(pubKey) { const rows = await db.all( - `SELECT json FROM ${PAIRING_AUTHORISATIONS_TABLE} WHERE primaryDevicePubKey = $primaryDevicePubKey AND isGranted = 1 ORDER BY secondaryDevicePubKey ASC;`, - { - $primaryDevicePubKey: primaryDevicePubKey, - } + `SELECT json FROM ${PAIRING_AUTHORISATIONS_TABLE} WHERE primaryDevicePubKey = $pubKey OR secondaryDevicePubKey = $pubKey;`, + { $pubKey: pubKey } ); - return map(rows, row => jsonToObject(row.json)); + + return rows.map(row => jsonToObject(row.json)); } async function createOrUpdatePairingAuthorisation(data) { const { primaryDevicePubKey, secondaryDevicePubKey, grantSignature } = data; // remove any existing authorisation for this pubkey (we allow only one secondary device for now) - await removePairingAuthorisationForPrimaryPubKey(primaryDevicePubKey); + await removePairingAuthorisationsFor(primaryDevicePubKey); await db.run( `INSERT OR REPLACE INTO ${PAIRING_AUTHORISATIONS_TABLE} ( @@ -1564,30 +1537,16 @@ async function createOrUpdatePairingAuthorisation(data) { ); } -async function removePairingAuthorisationForPrimaryPubKey(pubKey) { +async function removePairingAuthorisationsFor(pubKey) { await db.run( - `DELETE FROM ${PAIRING_AUTHORISATIONS_TABLE} WHERE primaryDevicePubKey = $primaryDevicePubKey;`, + `DELETE FROM ${PAIRING_AUTHORISATIONS_TABLE} WHERE primaryDevicePubKey = $pubKey OR secondaryDevicePubKey = $pubKey;`, { - $primaryDevicePubKey: pubKey, + $pubKey: pubKey, } ); } -async function removePairingAuthorisationForSecondaryPubKey(pubKey) { - await db.run( - `DELETE FROM ${PAIRING_AUTHORISATIONS_TABLE} WHERE secondaryDevicePubKey = $secondaryDevicePubKey;`, - { - $secondaryDevicePubKey: pubKey, - } - ); -} - -async function getSecondaryDevicesFor(primaryDevicePubKey) { - const authorisations = await getGrantAuthorisationsForPrimaryPubKey( - primaryDevicePubKey - ); - return map(authorisations, row => row.secondaryDevicePubKey); -} +const GUARD_NODE_TABLE = 'guardNodes'; async function getGuardNodes() { const nodes = await db.all(`SELECT ed25519PubKey FROM ${GUARD_NODE_TABLE};`); @@ -1620,42 +1579,8 @@ async function updateGuardNodes(nodes) { await db.run('END TRANSACTION;'); } -async function getPrimaryDeviceFor(secondaryDevicePubKey) { - const row = await db.get( - `SELECT primaryDevicePubKey FROM ${PAIRING_AUTHORISATIONS_TABLE} WHERE secondaryDevicePubKey = $secondaryDevicePubKey AND isGranted = 1;`, - { - $secondaryDevicePubKey: secondaryDevicePubKey, - } - ); - - if (!row) { - return null; - } - - return row.primaryDevicePubKey; -} - // Return all the paired pubkeys for a specific pubkey (excluded), // irrespective of their Primary or Secondary status. -async function getPairedDevicesFor(pubKey) { - let results = []; - - // get primary pubkey (only works if the pubkey is a secondary pubkey) - const primaryPubKey = await getPrimaryDeviceFor(pubKey); - if (primaryPubKey) { - results.push(primaryPubKey); - } - // get secondary pubkeys (only works if the pubkey is a primary pubkey) - const secondaryPubKeys = await getSecondaryDevicesFor( - primaryPubKey || pubKey - ); - results = results.concat(secondaryPubKeys); - - // ensure the input pubkey is not in the results - results = results.filter(x => x !== pubKey); - - return results; -} const ITEMS_TABLE = 'items'; async function createOrUpdateItem(data) { diff --git a/js/background.js b/js/background.js index 7e87ac953..eb794cadc 100644 --- a/js/background.js +++ b/js/background.js @@ -9,6 +9,7 @@ textsecure, Whisper, libloki, + libsession, libsignal, StringView, BlockedNumberController, @@ -1425,8 +1426,10 @@ Whisper.events.on('devicePairingRequestRejected', async pubKey => { await libloki.storage.removeContactPreKeyBundle(pubKey); - await libloki.storage.removePairingAuthorisationForSecondaryPubKey( - pubKey + + const pubKeyObject = new libsession.Types.PubKey(pubKey); + await libsession.Protocols.MultiDeviceProtocol.removePairingAuthorisations( + pubKeyObject ); }); @@ -1435,9 +1438,9 @@ if (isSecondaryDevice) { return; } - - await libloki.storage.removePairingAuthorisationForSecondaryPubKey( - pubKey + const pubKeyObject = new libsession.Types.PubKey(pubKey); + await libsession.Protocols.MultiDeviceProtocol.removePairingAuthorisations( + pubKeyObject ); await window.lokiFileServerAPI.updateOurDeviceMapping(); // TODO: we should ensure the message was sent and retry automatically if not @@ -1752,16 +1755,13 @@ return; } - let primaryDevice = null; - const authorisation = await libloki.storage.getGrantAuthorisationForSecondaryPubKey( - sender - ); - if (authorisation) { - primaryDevice = authorisation.primaryDevicePubKey; - } + const user = libsession.Types.PubKey.from(sender); + const primaryDevice = user + ? await libsession.Protocols.MultiDeviceProtocol.getPrimaryDevice(user) + : null; const conversation = ConversationController.get( - groupId || primaryDevice || sender + groupId || (primaryDevice && primaryDevice.key) || sender ); if (conversation) { diff --git a/js/models/conversations.js b/js/models/conversations.js index 3419e15b3..032c38a8c 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -876,15 +876,12 @@ // This is already the primary conversation return this; } - const authorisation = await window.libloki.storage.getAuthorisationForSecondaryPubKey( - this.id - ); - if (authorisation) { - return ConversationController.getOrCreateAndWait( - authorisation.primaryDevicePubKey, - 'private' - ); + + const device = window.libsession.Type.PubKey.from(this.id); + if (device) { + return ConversationController.getOrCreateAndWait(device.key, 'private'); } + // Something funky has happened return this; }, diff --git a/js/modules/data.d.ts b/js/modules/data.d.ts index c70fd17e1..f95dd8a6e 100644 --- a/js/modules/data.d.ts +++ b/js/modules/data.d.ts @@ -46,7 +46,7 @@ type PairingAuthorisation = { primaryDevicePubKey: string; secondaryDevicePubKey: string; requestSignature: ArrayBuffer; - grantSignature: ArrayBuffer | null; + grantSignature?: ArrayBuffer; }; type GuardNode = { @@ -153,25 +153,10 @@ export function removeAllContactSignedPreKeys(): Promise; export function createOrUpdatePairingAuthorisation( data: PairingAuthorisation ): Promise; -export function removePairingAuthorisationForSecondaryPubKey( - pubKey: string -): Promise; -export function getGrantAuthorisationsForPrimaryPubKey( +export function getPairingAuthorisationsFor( pubKey: string ): Promise>; -export function getGrantAuthorisationForSecondaryPubKey( - pubKey: string -): Promise; -export function getAuthorisationForSecondaryPubKey( - pubKey: string -): Promise; -export function getSecondaryDevicesFor( - primaryDevicePubKey: string -): Promise>; -export function getPrimaryDeviceFor( - secondaryDevicePubKey: string -): Promise; -export function getPairedDevicesFor(pubKey: string): Promise>; +export function removePairingAuthorisationsFor(pubKey: string): Promise; // Guard Nodes export function getGuardNodes(): Promise; diff --git a/js/modules/data.js b/js/modules/data.js index cfbc3d006..33e6122c1 100644 --- a/js/modules/data.js +++ b/js/modules/data.js @@ -89,13 +89,8 @@ module.exports = { removeAllContactSignedPreKeys, createOrUpdatePairingAuthorisation, - removePairingAuthorisationForSecondaryPubKey, - getGrantAuthorisationForSecondaryPubKey, - getAuthorisationForSecondaryPubKey, - getGrantAuthorisationsForPrimaryPubKey, - getSecondaryDevicesFor, - getPrimaryDeviceFor, - getPairedDevicesFor, + getPairingAuthorisationsFor, + removePairingAuthorisationsFor, getGuardNodes, updateGuardNodes, @@ -631,21 +626,20 @@ async function createOrUpdatePairingAuthorisation(data) { }); } -async function removePairingAuthorisationForSecondaryPubKey(pubKey) { - if (!pubKey) { - return; - } - await channels.removePairingAuthorisationForSecondaryPubKey(pubKey); +async function getPairingAuthorisationsFor(pubKey) { + const authorisations = channels.getPairingAuthorisationsFor(pubKey); + + return authorisations.map(authorisation => ({ + ...authorisation, + requestSignature: base64ToArrayBuffer(authorisation.requestSignature), + grantSignature: authorisation.grantSignature + ? base64ToArrayBuffer(authorisation.grantSignature) + : undefined, + })); } -async function getGrantAuthorisationForSecondaryPubKey(pubKey) { - return channels.getAuthorisationForSecondaryPubKey(pubKey, { - granted: true, - }); -} - -async function getGrantAuthorisationsForPrimaryPubKey(pubKey) { - return channels.getGrantAuthorisationsForPrimaryPubKey(pubKey); +async function removePairingAuthorisationsFor(pubKey) { + await channels.removePairingAuthorisationsFor(pubKey); } function getAuthorisationForSecondaryPubKey(pubKey) { diff --git a/js/modules/loki_file_server_api.js b/js/modules/loki_file_server_api.js index 61d5b27c8..a89452911 100644 --- a/js/modules/loki_file_server_api.js +++ b/js/modules/loki_file_server_api.js @@ -1,6 +1,5 @@ /* global log, libloki, window */ /* global storage: false */ -/* global Signal: false */ /* global log: false */ const LokiAppDotNetAPI = require('./loki_app_dot_net_api'); @@ -220,16 +219,11 @@ class LokiHomeServerInstance extends LokiFileServerInstance { async updateOurDeviceMapping() { const isPrimary = !storage.get('isSecondaryDevice'); - let authorisations; - if (isPrimary) { - authorisations = await Signal.Data.getGrantAuthorisationsForPrimaryPubKey( - this.ourKey - ); - } else { - authorisations = [ - await Signal.Data.getGrantAuthorisationForSecondaryPubKey(this.ourKey), - ]; - } + const ourKey = new window.libsession.Types.PubKey(this.ourKey); + const authorisations = await window.libsession.Protocols.MultiDeviceProtocol.getPairingAuthorisations( + ourKey + ); + return this._setOurDeviceMapping(authorisations, isPrimary); } diff --git a/libloki/storage.js b/libloki/storage.js index 9f057d17e..a5fbbe7be 100644 --- a/libloki/storage.js +++ b/libloki/storage.js @@ -186,12 +186,6 @@ await window.Signal.Data.createOrUpdatePairingAuthorisation(authorisation); } - function removePairingAuthorisationForSecondaryPubKey(pubKey) { - return window.Signal.Data.removePairingAuthorisationForSecondaryPubKey( - pubKey - ); - } - // Transforms signatures from base64 to ArrayBuffer! async function getGrantAuthorisationForSecondaryPubKey(secondaryPubKey) { const conversation = ConversationController.get(secondaryPubKey); @@ -264,15 +258,6 @@ saveContactPreKeyBundle, removeContactPreKeyBundle, verifyFriendRequestAcceptPreKey, - savePairingAuthorisation, - saveAllPairingAuthorisationsFor, - removePairingAuthorisationForSecondaryPubKey, - getGrantAuthorisationForSecondaryPubKey, - getAuthorisationForSecondaryPubKey, - getPairedDevicesFor, - getAllDevicePubKeysForPrimaryPubKey, - getSecondaryDevicesFor, - getPrimaryDeviceMapping, getGuardNodes, updateGuardNodes, }; diff --git a/libtextsecure/account_manager.js b/libtextsecure/account_manager.js index e88c1581a..5385ccd5b 100644 --- a/libtextsecure/account_manager.js +++ b/libtextsecure/account_manager.js @@ -3,6 +3,7 @@ textsecure, libsignal, libloki, + libsession, lokiFileServerAPI, mnemonic, btoa, @@ -604,8 +605,9 @@ e && e.stack ? e.stack : e ); // File server upload failed or message sending failed, we should rollback changes - await libloki.storage.removePairingAuthorisationForSecondaryPubKey( - secondaryDevicePubKey + const pubKey = new libsession.Types.PubKey(secondaryDevicePubKey); + await libsession.Protocols.MultiDeviceProtocol.removePairingAuthorisations( + pubKey ); await lokiFileServerAPI.updateOurDeviceMapping(); throw e; diff --git a/ts/session/index.ts b/ts/session/index.ts index 4aeab7901..20dcfd2cb 100644 --- a/ts/session/index.ts +++ b/ts/session/index.ts @@ -1,5 +1,6 @@ import * as Messages from './messages'; import * as Protocols from './protocols'; +import * as Types from './types'; // TODO: Do we export class instances here? // E.g diff --git a/ts/session/protocols/MultiDeviceProtocol.ts b/ts/session/protocols/MultiDeviceProtocol.ts index b144c20cf..d02ba4487 100644 --- a/ts/session/protocols/MultiDeviceProtocol.ts +++ b/ts/session/protocols/MultiDeviceProtocol.ts @@ -1,6 +1,91 @@ -// TODO: Populate this with multi device specific code, e.g getting linked devices for a user etc... -// We need to deprecate the multi device code we have in js and slowly transition to this file +import _ from 'lodash'; +import { + createOrUpdatePairingAuthorisation, + getPairingAuthorisationsFor, + PairingAuthorisation, + removePairingAuthorisationsFor, +} from '../../../js/modules/data'; +import { PrimaryPubKey, PubKey, SecondaryPubKey } from '../types'; -export function implementStuffHere() { - throw new Error("Don't call me :("); +/** + * Save pairing authorisation to the database. + * @param authorisation The pairing authorisation. + */ +export async function savePairingAuthorisation( + authorisation: PairingAuthorisation +): Promise { + return createOrUpdatePairingAuthorisation(authorisation); +} + +/** + * Get pairing authorisations for a given device. + * @param device The device to get pairing authorisations for. + */ +export async function getPairingAuthorisations( + device: PubKey +): Promise> { + return getPairingAuthorisationsFor(device.key); +} + +/** + * Remove all pairing authorisations for a given device. + * @param device The device to remove authorisation for. + */ +export async function removePairingAuthorisations( + device: PubKey +): Promise { + return removePairingAuthorisationsFor(device.key); +} + +/** + * Get all devices linked to a user. + * + * @param user The user to get all the devices from. + */ +export async function getAllDevices(user: PubKey): Promise> { + const authorisations = await getPairingAuthorisations(user); + const devices = _.flatMap( + authorisations, + ({ primaryDevicePubKey, secondaryDevicePubKey }) => [ + primaryDevicePubKey, + secondaryDevicePubKey, + ] + ); + + return [...new Set(devices)].map(p => new PubKey(p)); +} + +/** + * Get the primary device linked to a user. + * + * @param user The user to get primary device for. + */ +export async function getPrimaryDevice(user: PubKey): Promise { + const authorisations = await getPairingAuthorisations(user); + if (authorisations.length === 0) { + return user; + } + + const pubKey = PrimaryPubKey.from(authorisations[0].primaryDevicePubKey); + if (!pubKey) { + throw new Error(`Primary user public key is invalid for ${user.key}.`); + } + + return pubKey; +} + +/** + * Get all the secondary devices linked to a user. + * + * @param user The user to get the devices from. + */ +export async function getSecondaryDevices( + user: PubKey +): Promise> { + const primary = await getPrimaryDevice(user); + const authorisations = await getPairingAuthorisations(primary); + + return authorisations + .map(a => a.secondaryDevicePubKey) + .map(pubKey => new SecondaryPubKey(pubKey)); } diff --git a/ts/session/types/PubKey.ts b/ts/session/types/PubKey.ts index 5653db5b5..3d0489aef 100644 --- a/ts/session/types/PubKey.ts +++ b/ts/session/types/PubKey.ts @@ -26,3 +26,6 @@ export class PubKey { return false; } } + +export class PrimaryPubKey extends PubKey {} +export class SecondaryPubKey extends PubKey {} From b36b3e772556de86bca585d18d1c3517a6461dba Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 12 Jun 2020 16:09:23 +1000 Subject: [PATCH 02/12] Slowly replace old multi device functions --- js/background.js | 28 +++--- js/models/conversations.js | 2 +- libloki/storage.js | 104 +++++--------------- libtextsecure/message_receiver.js | 15 ++- ts/session/protocols/MultiDeviceProtocol.ts | 2 + 5 files changed, 46 insertions(+), 105 deletions(-) diff --git a/js/background.js b/js/background.js index eb794cadc..fc68416ea 100644 --- a/js/background.js +++ b/js/background.js @@ -1817,25 +1817,23 @@ activeAt = activeAt || Date.now(); } const ourPrimaryKey = window.storage.get('primaryDevicePubKey'); - const ourDevices = await libloki.storage.getAllDevicePubKeysForPrimaryPubKey( - ourPrimaryKey - ); - // TODO: We should probably just *not* send any secondary devices and - // just load them all and send FRs when we get the mapping - const isOurSecondaryDevice = - id !== ourPrimaryKey && - ourDevices && - ourDevices.some(devicePubKey => devicePubKey === id); - - if (isOurSecondaryDevice) { - await conversation.setSecondaryStatus(true, ourPrimaryKey); + if (ourPrimaryKey) { + const user = new libsession.Types.PubKey(ourPrimaryKey); + const secondaryDevices = await libsession.Protocols.MultiDeviceProtocol.getSecondaryDevices( + user + ); + if (secondaryDevices.some(device => device.key === id)) { + await conversation.setSecondaryStatus(true, ourPrimaryKey); + } } - const otherDevices = await libloki.storage.getPairedDevicesFor(id); - const devices = [id, ...otherDevices]; + const user = new libsession.Types.PubKey(id); + const devices = await libsession.Protocols.MultiDeviceProtocol.getAllDevices( + user + ); const deviceConversations = await Promise.all( devices.map(d => - ConversationController.getOrCreateAndWait(d, 'private') + ConversationController.getOrCreateAndWait(d.key, 'private') ) ); deviceConversations.forEach(device => { diff --git a/js/models/conversations.js b/js/models/conversations.js index 032c38a8c..d7089c87d 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -877,7 +877,7 @@ return this; } - const device = window.libsession.Type.PubKey.from(this.id); + const device = window.libsession.Types.PubKey.from(this.id); if (device) { return ConversationController.getOrCreateAndWait(device.key, 'private'); } diff --git a/libloki/storage.js b/libloki/storage.js index a5fbbe7be..82965b2ab 100644 --- a/libloki/storage.js +++ b/libloki/storage.js @@ -1,13 +1,10 @@ -/* global window, libsignal, textsecure, Signal, - lokiFileServerAPI, ConversationController */ +/* global window, libsignal, textsecure, + lokiFileServerAPI, */ // eslint-disable-next-line func-names (function() { window.libloki = window.libloki || {}; - const timers = {}; - const REFRESH_DELAY = 60 * 1000; - async function getPreKeyBundleForContact(pubKey) { const myKeyPair = await textsecure.storage.protocol.getIdentityKeyPair(); const identityKey = myKeyPair.pubKey; @@ -154,84 +151,26 @@ // if the device is a secondary device, // fetch the device mappings for its primary device - async function saveAllPairingAuthorisationsFor(pubKey) { - // Will be false if there is no timer - const cacheValid = timers[pubKey] > Date.now(); - if (cacheValid) { - return; - } - timers[pubKey] = Date.now() + REFRESH_DELAY; - const authorisations = await getPrimaryDeviceMapping(pubKey); - await Promise.all( - authorisations.map(authorisation => - savePairingAuthorisation(authorisation) - ) - ); + async function saveAllPairingAuthorisationsFor() { + throw new Error('Use MultiDeviceProtocol instead.'); } - async function savePairingAuthorisation(authorisation) { - if (!authorisation) { - return; - } - - // Ensure that we have a conversation for all the devices - const conversation = await ConversationController.getOrCreateAndWait( - authorisation.secondaryDevicePubKey, - 'private' - ); - await conversation.setSecondaryStatus( - true, - authorisation.primaryDevicePubKey - ); - await window.Signal.Data.createOrUpdatePairingAuthorisation(authorisation); + async function savePairingAuthorisation() { + throw new Error('Use MultiDeviceProtocol instead.'); } // Transforms signatures from base64 to ArrayBuffer! - async function getGrantAuthorisationForSecondaryPubKey(secondaryPubKey) { - const conversation = ConversationController.get(secondaryPubKey); - if (!conversation || conversation.isPublic() || conversation.isRss()) { - return null; - } - await saveAllPairingAuthorisationsFor(secondaryPubKey); - const authorisation = await window.Signal.Data.getGrantAuthorisationForSecondaryPubKey( - secondaryPubKey - ); - if (!authorisation) { - return null; - } - return { - ...authorisation, - requestSignature: Signal.Crypto.base64ToArrayBuffer( - authorisation.requestSignature - ), - grantSignature: Signal.Crypto.base64ToArrayBuffer( - authorisation.grantSignature - ), - }; + async function getGrantAuthorisationForSecondaryPubKey() { + throw new Error('Use MultiDeviceProtocol instead.'); } // Transforms signatures from base64 to ArrayBuffer! - async function getAuthorisationForSecondaryPubKey(secondaryPubKey) { - await saveAllPairingAuthorisationsFor(secondaryPubKey); - const authorisation = await window.Signal.Data.getAuthorisationForSecondaryPubKey( - secondaryPubKey - ); - if (!authorisation) { - return null; - } - return { - ...authorisation, - requestSignature: Signal.Crypto.base64ToArrayBuffer( - authorisation.requestSignature - ), - grantSignature: authorisation.grantSignature - ? Signal.Crypto.base64ToArrayBuffer(authorisation.grantSignature) - : null, - }; + async function getAuthorisationForSecondaryPubKey() { + throw new Error('Use MultiDeviceProtocol instead.'); } - function getSecondaryDevicesFor(primaryDevicePubKey) { - return window.Signal.Data.getSecondaryDevicesFor(primaryDevicePubKey); + function getSecondaryDevicesFor() { + throw new Error('Use MultiDeviceProtocol instead.'); } function getGuardNodes() { @@ -242,15 +181,12 @@ return window.Signal.Data.updateGuardNodes(nodes); } - async function getAllDevicePubKeysForPrimaryPubKey(primaryDevicePubKey) { - await saveAllPairingAuthorisationsFor(primaryDevicePubKey); - const secondaryPubKeys = - (await getSecondaryDevicesFor(primaryDevicePubKey)) || []; - return secondaryPubKeys.concat(primaryDevicePubKey); + async function getAllDevicePubKeysForPrimaryPubKey() { + throw new Error('Use MultiDeviceProtocol instead.'); } - function getPairedDevicesFor(pubkey) { - return window.Signal.Data.getPairedDevicesFor(pubkey); + function getPairedDevicesFor() { + throw new Error('use MultiDeviceProtocol instead'); } window.libloki.storage = { @@ -258,6 +194,14 @@ saveContactPreKeyBundle, removeContactPreKeyBundle, verifyFriendRequestAcceptPreKey, + getAllDevicePubKeysForPrimaryPubKey, + getPairedDevicesFor, + getSecondaryDevicesFor, + getPrimaryDeviceMapping, + saveAllPairingAuthorisationsFor, + savePairingAuthorisation, + getGrantAuthorisationForSecondaryPubKey, + getAuthorisationForSecondaryPubKey, getGuardNodes, updateGuardNodes, }; diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js index 4aecb96e2..6dfd31aa8 100644 --- a/libtextsecure/message_receiver.js +++ b/libtextsecure/message_receiver.js @@ -1720,16 +1720,13 @@ MessageReceiver.prototype.extend({ async handleSyncMessage(envelope, syncMessage) { // We should only accept sync messages from our devices const ourNumber = textsecure.storage.user.getNumber(); - const ourPrimaryNumber = window.storage.get('primaryDevicePubKey'); - const ourOtherDevices = await libloki.storage.getAllDevicePubKeysForPrimaryPubKey( - ourPrimaryNumber + const user = new libsession.Types.PubKey(ourNumber); + const ourDevices = await libsession.Protocols.MultiDeviceProtocol.getAllDevices( + user + ); + const validSyncSender = ourDevices.some( + device => device.key === envelope.source ); - const ourDevices = new Set([ - ourNumber, - ourPrimaryNumber, - ...ourOtherDevices, - ]); - const validSyncSender = ourDevices.has(envelope.source); if (!validSyncSender) { throw new Error( "Received sync message from a device we aren't paired with" diff --git a/ts/session/protocols/MultiDeviceProtocol.ts b/ts/session/protocols/MultiDeviceProtocol.ts index d02ba4487..7428b1dba 100644 --- a/ts/session/protocols/MultiDeviceProtocol.ts +++ b/ts/session/protocols/MultiDeviceProtocol.ts @@ -7,6 +7,8 @@ import { } from '../../../js/modules/data'; import { PrimaryPubKey, PubKey, SecondaryPubKey } from '../types'; +// TODO: We should fetch mappings when we can and only fetch them once every 5 minutes or something + /** * Save pairing authorisation to the database. * @param authorisation The pairing authorisation. From 7a6ea97efbed4301b299800bd5cd1d634d0f8424 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Mon, 15 Jun 2020 09:20:00 +1000 Subject: [PATCH 03/12] Handle strings in MultiDeviceProtocol --- js/background.js | 15 +- js/models/conversations.js | 9 +- js/modules/loki_file_server_api.js | 3 +- libtextsecure/account_manager.js | 3 +- libtextsecure/message_receiver.js | 3 +- ts/session/protocols/MultiDeviceProtocol.ts | 167 +++++++++++--------- ts/session/protocols/index.ts | 4 +- tslint.json | 2 +- 8 files changed, 112 insertions(+), 94 deletions(-) diff --git a/js/background.js b/js/background.js index fc68416ea..cada04a5f 100644 --- a/js/background.js +++ b/js/background.js @@ -1426,10 +1426,8 @@ Whisper.events.on('devicePairingRequestRejected', async pubKey => { await libloki.storage.removeContactPreKeyBundle(pubKey); - - const pubKeyObject = new libsession.Types.PubKey(pubKey); await libsession.Protocols.MultiDeviceProtocol.removePairingAuthorisations( - pubKeyObject + pubKey ); }); @@ -1438,9 +1436,8 @@ if (isSecondaryDevice) { return; } - const pubKeyObject = new libsession.Types.PubKey(pubKey); await libsession.Protocols.MultiDeviceProtocol.removePairingAuthorisations( - pubKeyObject + pubKey ); await window.lokiFileServerAPI.updateOurDeviceMapping(); // TODO: we should ensure the message was sent and retry automatically if not @@ -1755,6 +1752,8 @@ return; } + // A sender here could be referring to a group. + // Groups don't have primary devices so we need to take that into consideration. const user = libsession.Types.PubKey.from(sender); const primaryDevice = user ? await libsession.Protocols.MultiDeviceProtocol.getPrimaryDevice(user) @@ -1818,18 +1817,16 @@ } const ourPrimaryKey = window.storage.get('primaryDevicePubKey'); if (ourPrimaryKey) { - const user = new libsession.Types.PubKey(ourPrimaryKey); const secondaryDevices = await libsession.Protocols.MultiDeviceProtocol.getSecondaryDevices( - user + ourPrimaryKey ); if (secondaryDevices.some(device => device.key === id)) { await conversation.setSecondaryStatus(true, ourPrimaryKey); } } - const user = new libsession.Types.PubKey(id); const devices = await libsession.Protocols.MultiDeviceProtocol.getAllDevices( - user + id ); const deviceConversations = await Promise.all( devices.map(d => diff --git a/js/models/conversations.js b/js/models/conversations.js index d7089c87d..48997b89c 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -879,7 +879,14 @@ const device = window.libsession.Types.PubKey.from(this.id); if (device) { - return ConversationController.getOrCreateAndWait(device.key, 'private'); + const primary = await window.libsession.Protocols.MultiDeviceProtocol.getPrimaryDevice( + device + ); + + return ConversationController.getOrCreateAndWait( + primary.key, + 'private' + ); } // Something funky has happened diff --git a/js/modules/loki_file_server_api.js b/js/modules/loki_file_server_api.js index a89452911..b6f87b175 100644 --- a/js/modules/loki_file_server_api.js +++ b/js/modules/loki_file_server_api.js @@ -219,9 +219,8 @@ class LokiHomeServerInstance extends LokiFileServerInstance { async updateOurDeviceMapping() { const isPrimary = !storage.get('isSecondaryDevice'); - const ourKey = new window.libsession.Types.PubKey(this.ourKey); const authorisations = await window.libsession.Protocols.MultiDeviceProtocol.getPairingAuthorisations( - ourKey + this.ourKey ); return this._setOurDeviceMapping(authorisations, isPrimary); diff --git a/libtextsecure/account_manager.js b/libtextsecure/account_manager.js index 5385ccd5b..563a4baa5 100644 --- a/libtextsecure/account_manager.js +++ b/libtextsecure/account_manager.js @@ -605,9 +605,8 @@ e && e.stack ? e.stack : e ); // File server upload failed or message sending failed, we should rollback changes - const pubKey = new libsession.Types.PubKey(secondaryDevicePubKey); await libsession.Protocols.MultiDeviceProtocol.removePairingAuthorisations( - pubKey + secondaryDevicePubKey ); await lokiFileServerAPI.updateOurDeviceMapping(); throw e; diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js index 6dfd31aa8..7c00b7b8d 100644 --- a/libtextsecure/message_receiver.js +++ b/libtextsecure/message_receiver.js @@ -1720,9 +1720,8 @@ MessageReceiver.prototype.extend({ async handleSyncMessage(envelope, syncMessage) { // We should only accept sync messages from our devices const ourNumber = textsecure.storage.user.getNumber(); - const user = new libsession.Types.PubKey(ourNumber); const ourDevices = await libsession.Protocols.MultiDeviceProtocol.getAllDevices( - user + ourNumber ); const validSyncSender = ourDevices.some( device => device.key === envelope.source diff --git a/ts/session/protocols/MultiDeviceProtocol.ts b/ts/session/protocols/MultiDeviceProtocol.ts index 7428b1dba..737ea13b0 100644 --- a/ts/session/protocols/MultiDeviceProtocol.ts +++ b/ts/session/protocols/MultiDeviceProtocol.ts @@ -9,85 +9,102 @@ import { PrimaryPubKey, PubKey, SecondaryPubKey } from '../types'; // TODO: We should fetch mappings when we can and only fetch them once every 5 minutes or something -/** - * Save pairing authorisation to the database. - * @param authorisation The pairing authorisation. - */ -export async function savePairingAuthorisation( - authorisation: PairingAuthorisation -): Promise { - return createOrUpdatePairingAuthorisation(authorisation); -} - -/** - * Get pairing authorisations for a given device. - * @param device The device to get pairing authorisations for. - */ -export async function getPairingAuthorisations( - device: PubKey -): Promise> { - return getPairingAuthorisationsFor(device.key); -} - -/** - * Remove all pairing authorisations for a given device. - * @param device The device to remove authorisation for. - */ -export async function removePairingAuthorisations( - device: PubKey -): Promise { - return removePairingAuthorisationsFor(device.key); -} - -/** - * Get all devices linked to a user. - * - * @param user The user to get all the devices from. - */ -export async function getAllDevices(user: PubKey): Promise> { - const authorisations = await getPairingAuthorisations(user); - const devices = _.flatMap( - authorisations, - ({ primaryDevicePubKey, secondaryDevicePubKey }) => [ - primaryDevicePubKey, - secondaryDevicePubKey, - ] - ); - - return [...new Set(devices)].map(p => new PubKey(p)); -} - -/** - * Get the primary device linked to a user. - * - * @param user The user to get primary device for. - */ -export async function getPrimaryDevice(user: PubKey): Promise { - const authorisations = await getPairingAuthorisations(user); - if (authorisations.length === 0) { - return user; +/* + The reason we're exporing a class here instead of just exporting the functions directly is for the sake of testing. + We might want to stub out specific functions inside the multi device protocol itself but when export functions directly then it's not possible without weird hacks. +*/ +// tslint:disable-next-line: no-unnecessary-class +export class MultiDeviceProtocol { + /** + * Save pairing authorisation to the database. + * @param authorisation The pairing authorisation. + */ + public static async savePairingAuthorisation( + authorisation: PairingAuthorisation + ): Promise { + return createOrUpdatePairingAuthorisation(authorisation); } - const pubKey = PrimaryPubKey.from(authorisations[0].primaryDevicePubKey); - if (!pubKey) { - throw new Error(`Primary user public key is invalid for ${user.key}.`); + /** + * Get pairing authorisations for a given device. + * @param device The device to get pairing authorisations for. + */ + public static async getPairingAuthorisations( + device: PubKey | string + ): Promise> { + const pubKey = typeof device === 'string' ? new PubKey(device) : device; + + return getPairingAuthorisationsFor(pubKey.key); } - return pubKey; -} + /** + * Remove all pairing authorisations for a given device. + * @param device The device to remove authorisation for. + */ + public static async removePairingAuthorisations( + device: PubKey | string + ): Promise { + const pubKey = typeof device === 'string' ? new PubKey(device) : device; -/** - * Get all the secondary devices linked to a user. - * - * @param user The user to get the devices from. - */ -export async function getSecondaryDevices( - user: PubKey -): Promise> { - const primary = await getPrimaryDevice(user); - const authorisations = await getPairingAuthorisations(primary); + return removePairingAuthorisationsFor(pubKey.key); + } - return authorisations - .map(a => a.secondaryDevicePubKey) - .map(pubKey => new SecondaryPubKey(pubKey)); + /** + * Get all devices linked to a user. + * + * @param user The user to get all the devices from. + */ + public static async getAllDevices( + user: PubKey | string + ): Promise> { + const pubKey = typeof user === 'string' ? new PubKey(user) : user; + const authorisations = await this.getPairingAuthorisations(pubKey); + const devices = _.flatMap( + authorisations, + ({ primaryDevicePubKey, secondaryDevicePubKey }) => [ + primaryDevicePubKey, + secondaryDevicePubKey, + ] + ); + + return [...new Set(devices)].map(p => new PubKey(p)); + } + + /** + * Get the primary device linked to a user. + * + * @param user The user to get primary device for. + */ + public static async getPrimaryDevice( + user: PubKey | string + ): Promise { + const pubKey = typeof user === 'string' ? new PubKey(user) : user; + const authorisations = await this.getPairingAuthorisations(pubKey); + if (authorisations.length === 0) { + return pubKey; + } + + const primary = PrimaryPubKey.from(authorisations[0].primaryDevicePubKey); + if (!primary) { + throw new Error(`Primary user public key is invalid for ${pubKey.key}.`); + } + + return primary; + } + + /** + * Get all the secondary devices linked to a user. + * + * @param user The user to get the devices from. + */ + public static async getSecondaryDevices( + user: PubKey | string + ): Promise> { + const primary = await this.getPrimaryDevice(user); + const authorisations = await this.getPairingAuthorisations(primary); + + return authorisations + .map(a => a.secondaryDevicePubKey) + .map(pubKey => new SecondaryPubKey(pubKey)); + } } diff --git a/ts/session/protocols/index.ts b/ts/session/protocols/index.ts index 38fe021c0..375ad9391 100644 --- a/ts/session/protocols/index.ts +++ b/ts/session/protocols/index.ts @@ -1,4 +1,4 @@ import { SessionProtocol } from './SessionProtocol'; -import * as MultiDeviceProtocol from './MultiDeviceProtocol'; +export * from './MultiDeviceProtocol'; -export { SessionProtocol, MultiDeviceProtocol }; +export { SessionProtocol }; diff --git a/tslint.json b/tslint.json index f9428ed5e..628e59a93 100644 --- a/tslint.json +++ b/tslint.json @@ -92,7 +92,7 @@ "function-name": [ true, { - "function-regex": "^[a-z][\\w\\d]+$", + "function-regex": "^_?[a-z][\\w\\d]+$", "method-regex": "^[a-z][\\w\\d]+$", "private-method-regex": "^[a-z][\\w\\d]+$", "protected-method-regex": "^[a-z][\\w\\d]+$", From 7942ba086fc418b71a6a9670699b5e58d0c2368f Mon Sep 17 00:00:00 2001 From: Mikunj Date: Mon, 15 Jun 2020 10:36:43 +1000 Subject: [PATCH 04/12] Fetch device mappings from server when needed --- js/modules/loki_file_server_api.d.ts | 13 +++ ts/session/protocols/MultiDeviceProtocol.ts | 87 ++++++++++++++++++++- ts/window/index.ts | 2 + tslint.json | 4 +- 4 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 js/modules/loki_file_server_api.d.ts diff --git a/js/modules/loki_file_server_api.d.ts b/js/modules/loki_file_server_api.d.ts new file mode 100644 index 000000000..a1c1b0a71 --- /dev/null +++ b/js/modules/loki_file_server_api.d.ts @@ -0,0 +1,13 @@ +interface DeviceMappingAnnotation { + isPrimary: boolean; + authorisations: Array<{ + primaryDevicePubKey: string; + secondaryDevicePubKey: string; + requestSignature: string; // base64 + grantSignature: string; // base64 + }>; +} + +interface LokiFileServerInstance { + getUserDeviceMapping(pubKey: string): Promise; +} diff --git a/ts/session/protocols/MultiDeviceProtocol.ts b/ts/session/protocols/MultiDeviceProtocol.ts index 737ea13b0..9b41cb945 100644 --- a/ts/session/protocols/MultiDeviceProtocol.ts +++ b/ts/session/protocols/MultiDeviceProtocol.ts @@ -6,8 +6,8 @@ import { removePairingAuthorisationsFor, } from '../../../js/modules/data'; import { PrimaryPubKey, PubKey, SecondaryPubKey } from '../types'; - -// TODO: We should fetch mappings when we can and only fetch them once every 5 minutes or something +import { UserUtil } from '../../util'; +import { lokiFileServerAPI } from '../../window'; /* The reason we're exporing a class here instead of just exporting the functions directly is for the sake of testing. @@ -15,6 +15,88 @@ import { PrimaryPubKey, PubKey, SecondaryPubKey } from '../types'; */ // tslint:disable-next-line: no-unnecessary-class export class MultiDeviceProtocol { + public static refreshDelay: number = 5 * 1000 * 1000; // 5 minutes + private static lastFetch: { [device: string]: number } = {}; + + /** + * Fetch pairing authorisations from the file server if needed. + * This shouldn't be called outside of the MultiDeviceProtocol file, it is public so it can be stubbed in tests. + * + * This will fetch authorisations if: + * - It is not one of our device + * - The time since last fetch is more than refresh delay + */ + public static async _fetchPairingAuthorisationsIfNeeded( + device: PubKey + ): Promise { + // This return here stops an infinite loop when we get all our other devices + const ourKey = await UserUtil.getCurrentDevicePubKey(); + if (!ourKey || device.key === ourKey) { + return; + } + + // We always prefer our local pairing over the one on the server + const ourDevices = await this.getAllDevices(ourKey); + if (ourDevices.some(d => d.key === device.key)) { + return; + } + + // Only fetch if we hit the refresh delay + const lastFetchTime = this.lastFetch[device.key]; + if (lastFetchTime && lastFetchTime + this.refreshDelay < Date.now()) { + return; + } + + this.lastFetch[device.key] = Date.now(); + + try { + const authorisations = await this.fetchPairingAuthorisations(device); + // TODO: validate? + await Promise.all(authorisations.map(this.savePairingAuthorisation)); + } catch (e) { + // Something went wrong, let it re-try another time + this.lastFetch[device.key] = lastFetchTime; + } + } + + /** + * This function shouldn't be called outside of tests!! + */ + public static _resetFetchCache() { + this.lastFetch = {}; + } + + /** + * Fetch pairing authorisations for the given device from the file server. + * This function will not save the authorisations to the database. + * + * @param device The device to fetch the authorisation for. + */ + public static async fetchPairingAuthorisations( + device: PubKey + ): Promise> { + if (!lokiFileServerAPI) { + throw new Error('lokiFileServerAPI is not initialised.'); + } + + const mapping = await lokiFileServerAPI.getUserDeviceMapping(device.key); + // TODO: Filter out invalid authorisations + + return mapping.authorisations.map( + ({ + primaryDevicePubKey, + secondaryDevicePubKey, + requestSignature, + grantSignature, + }) => ({ + primaryDevicePubKey, + secondaryDevicePubKey, + requestSignature: Buffer.from(requestSignature, 'base64').buffer, + grantSignature: Buffer.from(grantSignature, 'base64').buffer, + }) + ); + } + /** * Save pairing authorisation to the database. * @param authorisation The pairing authorisation. @@ -33,6 +115,7 @@ export class MultiDeviceProtocol { device: PubKey | string ): Promise> { const pubKey = typeof device === 'string' ? new PubKey(device) : device; + await this._fetchPairingAuthorisationsIfNeeded(pubKey); return getPairingAuthorisationsFor(pubKey.key); } diff --git a/ts/window/index.ts b/ts/window/index.ts index 4688cde7b..6533ff1c5 100644 --- a/ts/window/index.ts +++ b/ts/window/index.ts @@ -76,6 +76,7 @@ interface WindowInterface extends Window { lokiMessageAPI: LokiMessageAPI; lokiPublicChatAPI: LokiPublicChatFactoryAPI; + lokiFileServerAPI: LokiFileServerInstance; } // In the case for tests @@ -140,3 +141,4 @@ export const textsecure = window.textsecure; export const lokiMessageAPI = window.lokiMessageAPI; export const lokiPublicChatAPI = window.lokiPublicChatAPI; +export const lokiFileServerAPI = window.lokiFileServerAPI; diff --git a/tslint.json b/tslint.json index 628e59a93..6059de7c7 100644 --- a/tslint.json +++ b/tslint.json @@ -93,10 +93,10 @@ true, { "function-regex": "^_?[a-z][\\w\\d]+$", - "method-regex": "^[a-z][\\w\\d]+$", + "method-regex": "^_?[a-z][\\w\\d]+$", "private-method-regex": "^[a-z][\\w\\d]+$", "protected-method-regex": "^[a-z][\\w\\d]+$", - "static-method-regex": "^[a-zA-Z][\\w\\d]+$" + "static-method-regex": "^_?[a-zA-Z][\\w\\d]+$" } ], From 7b7d8c0e2a1376f3d516648a8eabbcd8cbfe6c8b Mon Sep 17 00:00:00 2001 From: Mikunj Date: Mon, 15 Jun 2020 13:57:03 +1000 Subject: [PATCH 05/12] Remove multi device from libloki --- js/delivery_receipts.js | 11 ++-- js/models/conversations.js | 7 ++- js/views/create_group_dialog_view.js | 2 +- libloki/api.js | 10 ---- libloki/storage.js | 75 ---------------------------- libtextsecure/account_manager.js | 8 +-- libtextsecure/message_receiver.js | 4 +- libtextsecure/outgoing_message.js | 4 +- libtextsecure/sendmessage.js | 4 +- ts/receiver/receiver.ts | 29 ++++------- 10 files changed, 28 insertions(+), 126 deletions(-) diff --git a/js/delivery_receipts.js b/js/delivery_receipts.js index c2fa998ef..1201cca60 100644 --- a/js/delivery_receipts.js +++ b/js/delivery_receipts.js @@ -31,18 +31,13 @@ this.remove(receipts); return receipts; }, - async getTargetMessage(source, messages) { + async getTargetMessage(originalSource, messages) { if (messages.length === 0) { return null; } - const authorisation = await libloki.storage.getGrantAuthorisationForSecondaryPubKey( - source - ); - if (authorisation) { - // eslint-disable-next-line no-param-reassign - source = authorisation.primaryDevicePubKey; - } + const primary = await window.libsession.Protocols.MultiDeviceProtocol.getPrimaryDevice(originalSource); + const source = primary.key; const message = messages.find( item => !item.isIncoming() && source === item.get('conversationId') diff --git a/js/models/conversations.js b/js/models/conversations.js index 48997b89c..e39e7f4c5 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -211,10 +211,9 @@ return true; } - const ourDevices = await window.libloki.storage.getPairedDevicesFor( - this.ourNumber - ); - return ourDevices.includes(this.id); + const ourDevices = await window.libsession.Protocols.MultiDeviceProtocol.getAllDevices(this.ourNumber); + + return ourDevices.some(device => device.key === this.id); }, isOurLocalDevice() { return this.id === this.ourNumber; diff --git a/js/views/create_group_dialog_view.js b/js/views/create_group_dialog_view.js index a74074e6d..c9000c868 100644 --- a/js/views/create_group_dialog_view.js +++ b/js/views/create_group_dialog_view.js @@ -197,7 +197,7 @@ // exists in group, but hasn't yet synced with its other devices. const getDevicesForRemoved = async () => { const promises = notPresentInNew.map(member => - libloki.storage.getPairedDevicesFor(member) + window.libsession.Protocols.MultiDeviceProtocol.getAllDevices(member) ); const devices = _.flatten(await Promise.all(promises)); diff --git a/libloki/api.js b/libloki/api.js index 179d0d426..eca6cfce4 100644 --- a/libloki/api.js +++ b/libloki/api.js @@ -74,15 +74,6 @@ } } - // Returns the primary device pubkey for this secondary device pubkey - // or the same pubkey if there is no other device - async function getPrimaryDevicePubkey(pubKey) { - const authorisation = await window.libloki.storage.getGrantAuthorisationForSecondaryPubKey( - pubKey - ); - return authorisation ? authorisation.primaryDevicePubKey : pubKey; - } - async function sendSessionEstablishedMessage(pubKey) { // This message shouldn't be routed through multi-device. // It needs to go directly to the pubKey specified. @@ -330,7 +321,6 @@ createContactSyncProtoMessage, createGroupSyncProtoMessage, createOpenGroupsSyncProtoMessage, - getPrimaryDevicePubkey, debug, }; })(); diff --git a/libloki/storage.js b/libloki/storage.js index 82965b2ab..12a881418 100644 --- a/libloki/storage.js +++ b/libloki/storage.js @@ -114,65 +114,6 @@ } } - // fetches device mappings from server. - async function getPrimaryDeviceMapping(pubKey) { - if (typeof lokiFileServerAPI === 'undefined') { - // If this is not defined then we are initiating a pairing - return []; - } - const deviceMapping = await lokiFileServerAPI.getUserDeviceMapping(pubKey); - if (!deviceMapping) { - return []; - } - let authorisations = deviceMapping.authorisations || []; - if (deviceMapping.isPrimary === '0') { - const { primaryDevicePubKey } = - authorisations.find( - authorisation => - authorisation && authorisation.secondaryDevicePubKey === pubKey - ) || {}; - if (primaryDevicePubKey) { - // do NOT call getprimaryDeviceMapping recursively - // in case both devices are out of sync and think they are - // each others' secondary pubkey. - const primaryDeviceMapping = await lokiFileServerAPI.getUserDeviceMapping( - primaryDevicePubKey - ); - if (!primaryDeviceMapping) { - return []; - } - ({ authorisations } = primaryDeviceMapping); - } - } - - // filter out any invalid authorisations - return authorisations.filter(a => a && typeof a === 'object') || []; - } - - // if the device is a secondary device, - // fetch the device mappings for its primary device - async function saveAllPairingAuthorisationsFor() { - throw new Error('Use MultiDeviceProtocol instead.'); - } - - async function savePairingAuthorisation() { - throw new Error('Use MultiDeviceProtocol instead.'); - } - - // Transforms signatures from base64 to ArrayBuffer! - async function getGrantAuthorisationForSecondaryPubKey() { - throw new Error('Use MultiDeviceProtocol instead.'); - } - - // Transforms signatures from base64 to ArrayBuffer! - async function getAuthorisationForSecondaryPubKey() { - throw new Error('Use MultiDeviceProtocol instead.'); - } - - function getSecondaryDevicesFor() { - throw new Error('Use MultiDeviceProtocol instead.'); - } - function getGuardNodes() { return window.Signal.Data.getGuardNodes(); } @@ -181,27 +122,11 @@ return window.Signal.Data.updateGuardNodes(nodes); } - async function getAllDevicePubKeysForPrimaryPubKey() { - throw new Error('Use MultiDeviceProtocol instead.'); - } - - function getPairedDevicesFor() { - throw new Error('use MultiDeviceProtocol instead'); - } - window.libloki.storage = { getPreKeyBundleForContact, saveContactPreKeyBundle, removeContactPreKeyBundle, verifyFriendRequestAcceptPreKey, - getAllDevicePubKeysForPrimaryPubKey, - getPairedDevicesFor, - getSecondaryDevicesFor, - getPrimaryDeviceMapping, - saveAllPairingAuthorisationsFor, - savePairingAuthorisation, - getGrantAuthorisationForSecondaryPubKey, - getAuthorisationForSecondaryPubKey, getGuardNodes, updateGuardNodes, }; diff --git a/libtextsecure/account_manager.js b/libtextsecure/account_manager.js index 563a4baa5..95c73451a 100644 --- a/libtextsecure/account_manager.js +++ b/libtextsecure/account_manager.js @@ -574,9 +574,8 @@ secondaryDevicePubKey, libloki.crypto.PairingType.GRANT ); - const existingAuthorisation = await libloki.storage.getAuthorisationForSecondaryPubKey( - secondaryDevicePubKey - ); + const authorisations = await libsession.Protocols.MultiDeviceProtocol.getPairingAuthorisations(secondaryDevicePubKey); + const existingAuthorisation = authorisations.some(pairing => pairing.secondaryDevicePubKey === secondaryDevicePubKey); if (!existingAuthorisation) { throw new Error( 'authoriseSecondaryDevice: request signature missing from database!' @@ -589,8 +588,9 @@ requestSignature, grantSignature, }; + // Update authorisation in database with the new grant signature - await libloki.storage.savePairingAuthorisation(authorisation); + await libsession.Protocols.MultiDeviceProtocol.savePairingAuthorisation(authorisation); // Try to upload to the file server and then send a message try { diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js index ace178e00..9b8db68f3 100644 --- a/libtextsecure/message_receiver.js +++ b/libtextsecure/message_receiver.js @@ -968,7 +968,7 @@ MessageReceiver.prototype.extend({ if (valid) { // Pairing dialog is open and is listening if (Whisper.events.isListenedTo('devicePairingRequestReceived')) { - await window.libloki.storage.savePairingAuthorisation(pairingRequest); + await window.libsession.Protocols.MultiDeviceProtocol.savePairingAuthorisation(pairingRequest); Whisper.events.trigger( 'devicePairingRequestReceived', pairingRequest.secondaryDevicePubKey @@ -1014,7 +1014,7 @@ MessageReceiver.prototype.extend({ window.storage.remove('secondaryDeviceStatus'); window.storage.put('isSecondaryDevice', true); window.storage.put('primaryDevicePubKey', primaryDevicePubKey); - await libloki.storage.savePairingAuthorisation(pairingAuthorisation); + await window.libsession.Protocols.MultiDeviceProtocol.savePairingAuthorisation(pairingAuthorisation); const primaryConversation = await ConversationController.getOrCreateAndWait( primaryDevicePubKey, 'private' diff --git a/libtextsecure/outgoing_message.js b/libtextsecure/outgoing_message.js index 0a2cfcd18..1a5869f16 100644 --- a/libtextsecure/outgoing_message.js +++ b/libtextsecure/outgoing_message.js @@ -431,13 +431,13 @@ OutgoingMessage.prototype = { const ourPubKey = textsecure.storage.user.getNumber(); const ourPrimaryPubkey = window.storage.get('primaryDevicePubKey'); const secondaryPubKeys = - (await window.libloki.storage.getSecondaryDevicesFor(ourPubKey)) || []; + (await window.libsession.Protocols.MultiDeviceProtocol.getSecondaryDevices(ourPubKey)) || []; let aliasedPubkey = devicePubKey; if (devicePubKey === ourPubKey) { aliasedPubkey = 'OUR_PUBKEY'; // should not happen } else if (devicePubKey === ourPrimaryPubkey) { aliasedPubkey = 'OUR_PRIMARY_PUBKEY'; - } else if (secondaryPubKeys.includes(devicePubKey)) { + } else if (secondaryPubKeys.some(device => device.key === devicePubKey)) { aliasedPubkey = 'OUR SECONDARY PUBKEY'; } libloki.api.debug.logSessionMessageSending( diff --git a/libtextsecure/sendmessage.js b/libtextsecure/sendmessage.js index 08774c4a0..5ccaca3c4 100644 --- a/libtextsecure/sendmessage.js +++ b/libtextsecure/sendmessage.js @@ -537,12 +537,12 @@ MessageSender.prototype = { window.storage.get('primaryDevicePubKey') || textsecure.storage.user.getNumber(); const allOurDevices = ( - await libloki.storage.getAllDevicePubKeysForPrimaryPubKey( + await window.libsession.Protocols.MultiDeviceProtocol.getAllDevices( primaryDeviceKey ) ) // Don't send to ourselves - .filter(pubKey => pubKey !== textsecure.storage.user.getNumber()); + .filter(pubKey => pubKey.key !== textsecure.storage.user.getNumber()); if (allOurDevices.length === 0) { return null; } diff --git a/ts/receiver/receiver.ts b/ts/receiver/receiver.ts index e6df8d2bb..abfee68f4 100644 --- a/ts/receiver/receiver.ts +++ b/ts/receiver/receiver.ts @@ -15,6 +15,8 @@ import { SignalService } from './../protobuf'; import { removeFromCache } from './cache'; import { toNumber } from 'lodash'; import { DataMessage } from '../session/messages/outgoing'; +import { MultiDeviceProtocol } from '../session/protocols'; +import { PubKey } from '../session/types'; export { handleEndSession, handleMediumGroupUpdate }; @@ -265,7 +267,7 @@ async function handleSecondaryDeviceFriendRequest(pubKey: string) { if (!c || !(await c.isFriendWithAnyDevice())) { return false; } - await window.libloki.storage.savePairingAuthorisation(authorisation); + await MultiDeviceProtocol.savePairingAuthorisation(authorisation); return true; } @@ -601,14 +603,11 @@ export async function handleDataMessage( const source = envelope.senderIdentity || senderPubKey; - const isOwnDevice = async (pubkey: string) => { - const primaryDevice = window.storage.get('primaryDevicePubKey'); - const secondaryDevices = await window.libloki.storage.getPairedDevicesFor( - primaryDevice - ); + const isOwnDevice = async (device: string) => { + const pubKey = new PubKey(device); + const allDevices = await MultiDeviceProtocol.getAllDevices(pubKey); - const allDevices = [primaryDevice, ...secondaryDevices]; - return allDevices.includes(pubkey); + return allDevices.some(device => PubKey.isEqual(device, pubKey)); }; const ownDevice = await isOwnDevice(source); @@ -773,13 +772,7 @@ export async function handleMessageEvent(event: any): Promise { // - group.id if it is a group message let conversationId = id; - const authorisation = await window.libloki.storage.getGrantAuthorisationForSecondaryPubKey( - source - ); - - const primarySource = - (authorisation && authorisation.primaryDevicePubKey) || source; - + const primarySource = await MultiDeviceProtocol.getPrimaryDevice(source); if (isGroupMessage) { /* handle one part of the group logic here: handle requesting info of a new group, @@ -789,7 +782,7 @@ export async function handleMessageEvent(event: any): Promise { const shouldReturn = await preprocessGroupMessage( source, message.group, - primarySource + primarySource.key ); // handleGroupMessage() can process fully a message in some cases @@ -800,9 +793,9 @@ export async function handleMessageEvent(event: any): Promise { } } - if (source !== ourNumber && authorisation) { + if (source !== ourNumber) { // Ignore auth from our devices - conversationId = authorisation.primaryDevicePubKey; + conversationId = primarySource.key; } // the conversation with the primary device of that source (can be the same as conversationOrigin) From ae44f0da0f28a671466044e6248e08bfbe0c4daa Mon Sep 17 00:00:00 2001 From: Mikunj Date: Mon, 15 Jun 2020 14:19:58 +1000 Subject: [PATCH 06/12] Remove old multi device functions --- js/delivery_receipts.js | 5 +-- js/models/conversations.js | 4 ++- js/models/message.d.ts | 1 - js/modules/data.js | 16 --------- js/views/create_group_dialog_view.js | 2 +- libloki/storage.js | 3 +- libtextsecure/account_manager.js | 12 +++++-- libtextsecure/message_receiver.js | 8 +++-- libtextsecure/outgoing_message.js | 4 ++- .../session/settings/SessionSettings.tsx | 19 ++++++----- .../settings/SessionSettingsHeader.tsx | 23 +++++++------ ts/receiver/receiver.ts | 4 +-- ts/session/sending/MessageQueue.ts | 14 +++----- ts/session/utils/SyncMessageUtils.ts | 10 +++--- ts/state/ducks/search.ts | 34 ++++++------------- 15 files changed, 70 insertions(+), 89 deletions(-) diff --git a/js/delivery_receipts.js b/js/delivery_receipts.js index 1201cca60..c6fb37b24 100644 --- a/js/delivery_receipts.js +++ b/js/delivery_receipts.js @@ -4,7 +4,6 @@ ConversationController, MessageController, _, - libloki, */ /* eslint-disable more/no-then */ @@ -36,7 +35,9 @@ return null; } - const primary = await window.libsession.Protocols.MultiDeviceProtocol.getPrimaryDevice(originalSource); + const primary = await window.libsession.Protocols.MultiDeviceProtocol.getPrimaryDevice( + originalSource + ); const source = primary.key; const message = messages.find( diff --git a/js/models/conversations.js b/js/models/conversations.js index e39e7f4c5..2bb2ba599 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -211,7 +211,9 @@ return true; } - const ourDevices = await window.libsession.Protocols.MultiDeviceProtocol.getAllDevices(this.ourNumber); + const ourDevices = await window.libsession.Protocols.MultiDeviceProtocol.getAllDevices( + this.ourNumber + ); return ourDevices.some(device => device.key === this.id); }, diff --git a/js/models/message.d.ts b/js/models/message.d.ts index 73a3c88aa..862c3ed81 100644 --- a/js/models/message.d.ts +++ b/js/models/message.d.ts @@ -1,4 +1,3 @@ - type MessageModelType = 'incoming' | 'outgoing' | 'friend-request'; export type EndSessionType = 'done' | 'ongoing'; diff --git a/js/modules/data.js b/js/modules/data.js index 33e6122c1..a71e0a566 100644 --- a/js/modules/data.js +++ b/js/modules/data.js @@ -642,14 +642,6 @@ async function removePairingAuthorisationsFor(pubKey) { await channels.removePairingAuthorisationsFor(pubKey); } -function getAuthorisationForSecondaryPubKey(pubKey) { - return channels.getAuthorisationForSecondaryPubKey(pubKey); -} - -function getSecondaryDevicesFor(primaryDevicePubKey) { - return channels.getSecondaryDevicesFor(primaryDevicePubKey); -} - function getGuardNodes() { return channels.getGuardNodes(); } @@ -658,14 +650,6 @@ function updateGuardNodes(nodes) { return channels.updateGuardNodes(nodes); } -function getPrimaryDeviceFor(secondaryDevicePubKey) { - return channels.getPrimaryDeviceFor(secondaryDevicePubKey); -} - -function getPairedDevicesFor(pubKey) { - return channels.getPairedDevicesFor(pubKey); -} - // Items const ITEM_KEYS = { diff --git a/js/views/create_group_dialog_view.js b/js/views/create_group_dialog_view.js index c9000c868..00d56ce5c 100644 --- a/js/views/create_group_dialog_view.js +++ b/js/views/create_group_dialog_view.js @@ -1,4 +1,4 @@ -/* global Whisper, i18n, textsecure, libloki, _ */ +/* global Whisper, i18n, textsecure, _ */ // eslint-disable-next-line func-names (function() { diff --git a/libloki/storage.js b/libloki/storage.js index 12a881418..a9eb3ea3b 100644 --- a/libloki/storage.js +++ b/libloki/storage.js @@ -1,5 +1,4 @@ -/* global window, libsignal, textsecure, - lokiFileServerAPI, */ +/* global window, libsignal, textsecure */ // eslint-disable-next-line func-names (function() { diff --git a/libtextsecure/account_manager.js b/libtextsecure/account_manager.js index 95c73451a..7536b9113 100644 --- a/libtextsecure/account_manager.js +++ b/libtextsecure/account_manager.js @@ -574,8 +574,12 @@ secondaryDevicePubKey, libloki.crypto.PairingType.GRANT ); - const authorisations = await libsession.Protocols.MultiDeviceProtocol.getPairingAuthorisations(secondaryDevicePubKey); - const existingAuthorisation = authorisations.some(pairing => pairing.secondaryDevicePubKey === secondaryDevicePubKey); + const authorisations = await libsession.Protocols.MultiDeviceProtocol.getPairingAuthorisations( + secondaryDevicePubKey + ); + const existingAuthorisation = authorisations.some( + pairing => pairing.secondaryDevicePubKey === secondaryDevicePubKey + ); if (!existingAuthorisation) { throw new Error( 'authoriseSecondaryDevice: request signature missing from database!' @@ -590,7 +594,9 @@ }; // Update authorisation in database with the new grant signature - await libsession.Protocols.MultiDeviceProtocol.savePairingAuthorisation(authorisation); + await libsession.Protocols.MultiDeviceProtocol.savePairingAuthorisation( + authorisation + ); // Try to upload to the file server and then send a message try { diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js index 9b8db68f3..9619e20ba 100644 --- a/libtextsecure/message_receiver.js +++ b/libtextsecure/message_receiver.js @@ -968,7 +968,9 @@ MessageReceiver.prototype.extend({ if (valid) { // Pairing dialog is open and is listening if (Whisper.events.isListenedTo('devicePairingRequestReceived')) { - await window.libsession.Protocols.MultiDeviceProtocol.savePairingAuthorisation(pairingRequest); + await window.libsession.Protocols.MultiDeviceProtocol.savePairingAuthorisation( + pairingRequest + ); Whisper.events.trigger( 'devicePairingRequestReceived', pairingRequest.secondaryDevicePubKey @@ -1014,7 +1016,9 @@ MessageReceiver.prototype.extend({ window.storage.remove('secondaryDeviceStatus'); window.storage.put('isSecondaryDevice', true); window.storage.put('primaryDevicePubKey', primaryDevicePubKey); - await window.libsession.Protocols.MultiDeviceProtocol.savePairingAuthorisation(pairingAuthorisation); + await window.libsession.Protocols.MultiDeviceProtocol.savePairingAuthorisation( + pairingAuthorisation + ); const primaryConversation = await ConversationController.getOrCreateAndWait( primaryDevicePubKey, 'private' diff --git a/libtextsecure/outgoing_message.js b/libtextsecure/outgoing_message.js index 1a5869f16..566f74964 100644 --- a/libtextsecure/outgoing_message.js +++ b/libtextsecure/outgoing_message.js @@ -431,7 +431,9 @@ OutgoingMessage.prototype = { const ourPubKey = textsecure.storage.user.getNumber(); const ourPrimaryPubkey = window.storage.get('primaryDevicePubKey'); const secondaryPubKeys = - (await window.libsession.Protocols.MultiDeviceProtocol.getSecondaryDevices(ourPubKey)) || []; + (await window.libsession.Protocols.MultiDeviceProtocol.getSecondaryDevices( + ourPubKey + )) || []; let aliasedPubkey = devicePubKey; if (devicePubKey === ourPubKey) { aliasedPubkey = 'OUR_PUBKEY'; // should not happen diff --git a/ts/components/session/settings/SessionSettings.tsx b/ts/components/session/settings/SessionSettings.tsx index 6bc427876..922c6709a 100644 --- a/ts/components/session/settings/SessionSettings.tsx +++ b/ts/components/session/settings/SessionSettings.tsx @@ -7,6 +7,9 @@ import { SessionButtonColor, SessionButtonType, } from '../SessionButton'; +import { UserUtil } from '../../../util'; +import { MultiDeviceProtocol } from '../../../session/protocols'; +import { PubKey } from '../../../session/types'; export enum SessionSettingCategory { Appearance = 'appearance', @@ -644,16 +647,14 @@ export class SettingsView extends React.Component { } } - private refreshLinkedDevice() { - const ourPubKey = window.textsecure.storage.user.getNumber(); + private async refreshLinkedDevice() { + const ourPubKey = await UserUtil.getCurrentDevicePubKey(); + if (ourPubKey) { + const pubKey = new PubKey(ourPubKey); + const devices = await MultiDeviceProtocol.getSecondaryDevices(pubKey); - window.libloki.storage - .getSecondaryDevicesFor(ourPubKey) - .then((pubKeys: any) => { - this.setState({ - linkedPubKeys: pubKeys, - }); - }); + this.setState({ linkedPubKeys: devices.map(d => d.key) }); + } } private async onKeyUp(event: any) { diff --git a/ts/components/session/settings/SessionSettingsHeader.tsx b/ts/components/session/settings/SessionSettingsHeader.tsx index 2c14e0dbf..21e663e96 100644 --- a/ts/components/session/settings/SessionSettingsHeader.tsx +++ b/ts/components/session/settings/SessionSettingsHeader.tsx @@ -3,6 +3,9 @@ import { SessionIconButton, SessionIconSize, SessionIconType } from '../icon'; import { SessionSettingCategory, SettingsViewProps } from './SessionSettings'; import { SessionButton } from '../SessionButton'; +import { UserUtil } from '../../../util'; +import { PubKey } from '../../../session/types'; +import { MultiDeviceProtocol } from '../../../session/protocols'; interface Props extends SettingsViewProps { // showLinkDeviceButton is used to completely hide the button while the settings password lock is displayed @@ -37,22 +40,20 @@ export class SettingsHeader extends React.Component { public componentDidMount() { if (!this.props.isSecondaryDevice) { window.Whisper.events.on('refreshLinkedDeviceList', async () => { - this.refreshLinkedDevice(); + void this.refreshLinkedDevice(); }); - this.refreshLinkedDevice(); + void this.refreshLinkedDevice(); } } - public refreshLinkedDevice() { - const ourPubKey = window.textsecure.storage.user.getNumber(); + public async refreshLinkedDevice() { + const ourPubKey = await UserUtil.getCurrentDevicePubKey(); + if (ourPubKey) { + const pubKey = new PubKey(ourPubKey); + const devices = await MultiDeviceProtocol.getSecondaryDevices(pubKey); - window.libloki.storage - .getSecondaryDevicesFor(ourPubKey) - .then((pubKeys: any) => { - this.setState({ - disableLinkDeviceButton: pubKeys && pubKeys.length > 0, - }); - }); + this.setState({ disableLinkDeviceButton: devices.length > 0 }); + } } public componentWillUnmount() { diff --git a/ts/receiver/receiver.ts b/ts/receiver/receiver.ts index abfee68f4..d36f8649b 100644 --- a/ts/receiver/receiver.ts +++ b/ts/receiver/receiver.ts @@ -605,9 +605,9 @@ export async function handleDataMessage( const isOwnDevice = async (device: string) => { const pubKey = new PubKey(device); - const allDevices = await MultiDeviceProtocol.getAllDevices(pubKey); + const allDevices = await MultiDeviceProtocol.getAllDevices(pubKey); - return allDevices.some(device => PubKey.isEqual(device, pubKey)); + return allDevices.some(d => PubKey.isEqual(d, pubKey)); }; const ownDevice = await isOwnDevice(source); diff --git a/ts/session/sending/MessageQueue.ts b/ts/session/sending/MessageQueue.ts index 43746d09f..5fc47191d 100644 --- a/ts/session/sending/MessageQueue.ts +++ b/ts/session/sending/MessageQueue.ts @@ -1,5 +1,3 @@ -import { getPairedDevicesFor } from '../../../js/modules/data'; - import { EventEmitter } from 'events'; import { MessageQueueInterface, @@ -20,7 +18,7 @@ import { } from '../utils'; import { PubKey } from '../types'; import { MessageSender } from '.'; -import { SessionProtocol } from '../protocols'; +import { MultiDeviceProtocol, SessionProtocol } from '../protocols'; import { UserUtil } from '../../util'; export class MessageQueue implements MessageQueueInterface { @@ -35,8 +33,7 @@ export class MessageQueue implements MessageQueueInterface { } public async sendUsingMultiDevice(user: PubKey, message: ContentMessage) { - const userLinked = await getPairedDevicesFor(user.key); - const userDevices = userLinked.map(d => new PubKey(d)); + const userDevices = await MultiDeviceProtocol.getAllDevices(user.key); await this.sendMessageToDevices(userDevices, message); } @@ -56,11 +53,10 @@ export class MessageQueue implements MessageQueueInterface { const currentDevice = await UserUtil.getCurrentDevicePubKey(); if (currentDevice) { - const otherDevices = await getPairedDevicesFor(currentDevice); - - const ourDevices = [currentDevice, ...otherDevices].map( - device => new PubKey(device) + const ourDevices = await MultiDeviceProtocol.getAllDevices( + currentDevice ); + await this.sendSyncMessage(message, ourDevices); // Remove our devices from currentDevices diff --git a/ts/session/utils/SyncMessageUtils.ts b/ts/session/utils/SyncMessageUtils.ts index 92c1116c0..16d828777 100644 --- a/ts/session/utils/SyncMessageUtils.ts +++ b/ts/session/utils/SyncMessageUtils.ts @@ -1,12 +1,10 @@ import * as _ from 'lodash'; import * as UserUtils from '../../util/user'; -import { - getAllConversations, - getPrimaryDeviceFor, -} from '../../../js/modules/data'; +import { getAllConversations } from '../../../js/modules/data'; import { ConversationController, Whisper } from '../../window'; import { ContentMessage, SyncMessage } from '../messages/outgoing'; +import { MultiDeviceProtocol } from '../protocols'; export function from(message: ContentMessage): SyncMessage | undefined { // const { timestamp, identifier } = message; @@ -30,7 +28,7 @@ export async function getSyncContacts(): Promise | undefined> { return []; } - const primaryDevice = await getPrimaryDeviceFor(thisDevice); + const primaryDevice = await MultiDeviceProtocol.getPrimaryDevice(thisDevice); const conversations = await getAllConversations({ ConversationCollection: Whisper.ConversationCollection, }); @@ -62,7 +60,7 @@ export async function getSyncContacts(): Promise | undefined> { const secondaryContacts = (await Promise.all(seondaryContactsPromise)) // Filter out our primary key if it was added here - .filter(c => c.id !== primaryDevice); + .filter(c => c.id !== primaryDevice.key); // Return unique contacts return _.uniqBy( diff --git a/ts/state/ducks/search.ts b/ts/state/ducks/search.ts index 671d43d6f..a3a21e79e 100644 --- a/ts/state/ducks/search.ts +++ b/ts/state/ducks/search.ts @@ -5,11 +5,7 @@ import { AdvancedSearchOptions, SearchOptions } from '../../types/Search'; import { trigger } from '../../shims/events'; import { getMessageModel } from '../../shims/Whisper'; import { cleanSearchTerm } from '../../util/cleanSearchTerm'; -import { - getPrimaryDeviceFor, - searchConversations, - searchMessages, -} from '../../../js/modules/data'; +import { searchConversations, searchMessages } from '../../../js/modules/data'; import { makeLookup } from '../../util/makeLookup'; import { @@ -19,6 +15,8 @@ import { RemoveAllConversationsActionType, SelectedConversationChangedActionType, } from './conversations'; +import { MultiDeviceProtocol } from '../../session/protocols'; +import { PubKey } from '../../session/types'; // State @@ -283,15 +281,13 @@ async function queryConversationsAndContacts( query ); - const ourPrimaryDevice = isSecondaryDevice - ? await getPrimaryDeviceFor(ourNumber) - : ourNumber; + const ourPrimaryDevice = await MultiDeviceProtocol.getPrimaryDevice( + ourNumber + ); - const resultPrimaryDevices: Array = await Promise.all( + const resultPrimaryDevices = await Promise.all( searchResults.map(async conversation => - conversation.id === ourPrimaryDevice - ? Promise.resolve(ourPrimaryDevice) - : getPrimaryDeviceFor(conversation.id) + MultiDeviceProtocol.getPrimaryDevice(conversation.id) ) ); @@ -303,18 +299,10 @@ async function queryConversationsAndContacts( const conversation = searchResults[i]; const primaryDevice = resultPrimaryDevices[i]; - if (primaryDevice) { - if (isSecondaryDevice && primaryDevice === ourPrimaryDevice) { - conversations.push(ourNumber); - } else { - conversations.push(primaryDevice); - } - } else if (conversation.type === 'direct') { - contacts.push(conversation.id); - } else if (conversation.type !== 'group') { - contacts.push(conversation.id); + if (isSecondaryDevice && PubKey.isEqual(primaryDevice, ourPrimaryDevice)) { + conversations.push(ourNumber); } else { - conversations.push(conversation.id); + conversations.push(primaryDevice.key); } } // Inject synthetic Note to Self entry if query matches localized 'Note to Self' From 2ed8aeaf30da6acafbe477b956ed540ca5bcdf53 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Tue, 16 Jun 2020 10:18:53 +1000 Subject: [PATCH 07/12] Lint --- ts/components/session/settings/SessionSettings.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ts/components/session/settings/SessionSettings.tsx b/ts/components/session/settings/SessionSettings.tsx index 922c6709a..9a3933f4d 100644 --- a/ts/components/session/settings/SessionSettings.tsx +++ b/ts/components/session/settings/SessionSettings.tsx @@ -83,10 +83,10 @@ export class SettingsView extends React.Component { window.Whisper.events.on('refreshLinkedDeviceList', async () => { setTimeout(() => { - this.refreshLinkedDevice(); + void this.refreshLinkedDevice(); }, 1000); }); - this.refreshLinkedDevice(); + void this.refreshLinkedDevice(); } public componentWillUnmount() { From c027490d2f96420c3a32d6ec6d40010e8e67ff3b Mon Sep 17 00:00:00 2001 From: Mikunj Date: Tue, 16 Jun 2020 13:07:18 +1000 Subject: [PATCH 08/12] Merge fixes --- js/modules/loki_file_server_api.d.ts | 3 ++- ts/receiver/receiver.ts | 7 ++++++- ts/session/protocols/MultiDeviceProtocol.ts | 7 ++++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/js/modules/loki_file_server_api.d.ts b/js/modules/loki_file_server_api.d.ts index a1c1b0a71..64695d6de 100644 --- a/js/modules/loki_file_server_api.d.ts +++ b/js/modules/loki_file_server_api.d.ts @@ -1,5 +1,5 @@ interface DeviceMappingAnnotation { - isPrimary: boolean; + isPrimary: string; authorisations: Array<{ primaryDevicePubKey: string; secondaryDevicePubKey: string; @@ -10,4 +10,5 @@ interface DeviceMappingAnnotation { interface LokiFileServerInstance { getUserDeviceMapping(pubKey: string): Promise; + clearOurDeviceMappingAnnotations(): Promise; } diff --git a/ts/receiver/receiver.ts b/ts/receiver/receiver.ts index d36f8649b..ef85abaf3 100644 --- a/ts/receiver/receiver.ts +++ b/ts/receiver/receiver.ts @@ -267,7 +267,12 @@ async function handleSecondaryDeviceFriendRequest(pubKey: string) { if (!c || !(await c.isFriendWithAnyDevice())) { return false; } - await MultiDeviceProtocol.savePairingAuthorisation(authorisation); + await MultiDeviceProtocol.savePairingAuthorisation({ + primaryDevicePubKey: authorisation.primaryDevicePubKey, + secondaryDevicePubKey: authorisation.secondaryDevicePubKey, + requestSignature: Buffer.from(authorisation.requestSignature).buffer, + grantSignature: Buffer.from(authorisation.grantSignature).buffer, + }); return true; } diff --git a/ts/session/protocols/MultiDeviceProtocol.ts b/ts/session/protocols/MultiDeviceProtocol.ts index 9b41cb945..f55667962 100644 --- a/ts/session/protocols/MultiDeviceProtocol.ts +++ b/ts/session/protocols/MultiDeviceProtocol.ts @@ -7,7 +7,6 @@ import { } from '../../../js/modules/data'; import { PrimaryPubKey, PubKey, SecondaryPubKey } from '../types'; import { UserUtil } from '../../util'; -import { lokiFileServerAPI } from '../../window'; /* The reason we're exporing a class here instead of just exporting the functions directly is for the sake of testing. @@ -75,11 +74,13 @@ export class MultiDeviceProtocol { public static async fetchPairingAuthorisations( device: PubKey ): Promise> { - if (!lokiFileServerAPI) { + if (!window.lokiFileServerAPI) { throw new Error('lokiFileServerAPI is not initialised.'); } - const mapping = await lokiFileServerAPI.getUserDeviceMapping(device.key); + const mapping = await window.lokiFileServerAPI.getUserDeviceMapping( + device.key + ); // TODO: Filter out invalid authorisations return mapping.authorisations.map( From 57ea59e5b7f9b160ed4ce33f0e4ccdf5099cf478 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Tue, 16 Jun 2020 14:38:39 +1000 Subject: [PATCH 09/12] Minor fixes --- ts/session/protocols/MultiDeviceProtocol.ts | 15 ++++++++------- tslint.json | 6 +++--- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/ts/session/protocols/MultiDeviceProtocol.ts b/ts/session/protocols/MultiDeviceProtocol.ts index f55667962..46a310e2f 100644 --- a/ts/session/protocols/MultiDeviceProtocol.ts +++ b/ts/session/protocols/MultiDeviceProtocol.ts @@ -10,7 +10,7 @@ import { UserUtil } from '../../util'; /* The reason we're exporing a class here instead of just exporting the functions directly is for the sake of testing. - We might want to stub out specific functions inside the multi device protocol itself but when export functions directly then it's not possible without weird hacks. + We might want to stub out specific functions inside the multi device protocol itself but when exporting functions directly then it's not possible without weird hacks. */ // tslint:disable-next-line: no-unnecessary-class export class MultiDeviceProtocol { @@ -18,14 +18,13 @@ export class MultiDeviceProtocol { private static lastFetch: { [device: string]: number } = {}; /** - * Fetch pairing authorisations from the file server if needed. - * This shouldn't be called outside of the MultiDeviceProtocol file, it is public so it can be stubbed in tests. + * Fetch pairing authorisations from the file server if needed and store it in the database. * * This will fetch authorisations if: * - It is not one of our device * - The time since last fetch is more than refresh delay */ - public static async _fetchPairingAuthorisationsIfNeeded( + public static async fetchPairingAuthorisationsIfNeeded( device: PubKey ): Promise { // This return here stops an infinite loop when we get all our other devices @@ -59,9 +58,11 @@ export class MultiDeviceProtocol { } /** - * This function shouldn't be called outside of tests!! + * Reset the pairing fetched cache. + * + * This will make it so the next call to `fetchPairingAuthorisationsIfNeeded` will fetch mappings from the server. */ - public static _resetFetchCache() { + public static resetFetchCache() { this.lastFetch = {}; } @@ -116,7 +117,7 @@ export class MultiDeviceProtocol { device: PubKey | string ): Promise> { const pubKey = typeof device === 'string' ? new PubKey(device) : device; - await this._fetchPairingAuthorisationsIfNeeded(pubKey); + await this.fetchPairingAuthorisationsIfNeeded(pubKey); return getPairingAuthorisationsFor(pubKey.key); } diff --git a/tslint.json b/tslint.json index 9eabe91ed..bc3e64df1 100644 --- a/tslint.json +++ b/tslint.json @@ -92,11 +92,11 @@ "function-name": [ true, { - "function-regex": "^_?[a-z][\\w\\d]+$", - "method-regex": "^_?[a-z][\\w\\d]+$", + "function-regex": "^[a-z][\\w\\d]+$", + "method-regex": "^[a-z][\\w\\d]+$", "private-method-regex": "^[a-z][\\w\\d]+$", "protected-method-regex": "^[a-z][\\w\\d]+$", - "static-method-regex": "^_?[a-zA-Z][\\w\\d]+$" + "static-method-regex": "^[a-zA-Z][\\w\\d]+$" } ], From a83ce4ee16611798560686adad0443fcecfc2472 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Tue, 16 Jun 2020 15:11:46 +1000 Subject: [PATCH 10/12] Review fixes --- js/modules/loki_file_server_api.d.ts | 14 ++++++++------ ts/session/index.ts | 2 +- ts/session/protocols/MultiDeviceProtocol.ts | 2 +- ts/state/ducks/search.ts | 17 ++++++++++++++--- 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/js/modules/loki_file_server_api.d.ts b/js/modules/loki_file_server_api.d.ts index 64695d6de..08ea08a5a 100644 --- a/js/modules/loki_file_server_api.d.ts +++ b/js/modules/loki_file_server_api.d.ts @@ -1,11 +1,13 @@ +interface FileServerPairingAuthorisation { + primaryDevicePubKey: string; + secondaryDevicePubKey: string; + requestSignature: string; // base64 + grantSignature: string; // base64 +} + interface DeviceMappingAnnotation { isPrimary: string; - authorisations: Array<{ - primaryDevicePubKey: string; - secondaryDevicePubKey: string; - requestSignature: string; // base64 - grantSignature: string; // base64 - }>; + authorisations: Array; } interface LokiFileServerInstance { diff --git a/ts/session/index.ts b/ts/session/index.ts index 20dcfd2cb..b0c1d659b 100644 --- a/ts/session/index.ts +++ b/ts/session/index.ts @@ -6,4 +6,4 @@ import * as Types from './types'; // E.g // export const messageQueue = new MessageQueue() -export { Messages, Protocols }; +export { Messages, Protocols, Types }; diff --git a/ts/session/protocols/MultiDeviceProtocol.ts b/ts/session/protocols/MultiDeviceProtocol.ts index 46a310e2f..0c2aebfdd 100644 --- a/ts/session/protocols/MultiDeviceProtocol.ts +++ b/ts/session/protocols/MultiDeviceProtocol.ts @@ -14,7 +14,7 @@ import { UserUtil } from '../../util'; */ // tslint:disable-next-line: no-unnecessary-class export class MultiDeviceProtocol { - public static refreshDelay: number = 5 * 1000 * 1000; // 5 minutes + public static refreshDelay: number = 5 * 60 * 1000; // 5 minutes private static lastFetch: { [device: string]: number } = {}; /** diff --git a/ts/state/ducks/search.ts b/ts/state/ducks/search.ts index a3a21e79e..e6ba4e857 100644 --- a/ts/state/ducks/search.ts +++ b/ts/state/ducks/search.ts @@ -299,10 +299,21 @@ async function queryConversationsAndContacts( const conversation = searchResults[i]; const primaryDevice = resultPrimaryDevices[i]; - if (isSecondaryDevice && PubKey.isEqual(primaryDevice, ourPrimaryDevice)) { - conversations.push(ourNumber); + if (primaryDevice) { + if ( + isSecondaryDevice && + PubKey.isEqual(primaryDevice, ourPrimaryDevice) + ) { + conversations.push(ourNumber); + } else { + conversations.push(primaryDevice.key); + } + } else if (conversation.type === 'direct') { + contacts.push(conversation.id); + } else if (conversation.type !== 'group') { + contacts.push(conversation.id); } else { - conversations.push(primaryDevice.key); + conversations.push(conversation.id); } } // Inject synthetic Note to Self entry if query matches localized 'Note to Self' From f681fd619d437ef5281ff53698def9a835e0ecad Mon Sep 17 00:00:00 2001 From: Mikunj Date: Wed, 17 Jun 2020 10:51:07 +1000 Subject: [PATCH 11/12] Added small helper functions --- js/models/conversations.js | 6 ++--- ts/session/protocols/MultiDeviceProtocol.ts | 27 +++++++++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/js/models/conversations.js b/js/models/conversations.js index 2bb2ba599..df7051338 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -211,11 +211,9 @@ return true; } - const ourDevices = await window.libsession.Protocols.MultiDeviceProtocol.getAllDevices( - this.ourNumber + return window.libsession.Protocols.MultiDeviceProtocol.isOurDevice( + this.id ); - - return ourDevices.some(device => device.key === this.id); }, isOurLocalDevice() { return this.id === this.ourNumber; diff --git a/ts/session/protocols/MultiDeviceProtocol.ts b/ts/session/protocols/MultiDeviceProtocol.ts index 0c2aebfdd..18a9a7138 100644 --- a/ts/session/protocols/MultiDeviceProtocol.ts +++ b/ts/session/protocols/MultiDeviceProtocol.ts @@ -192,4 +192,31 @@ export class MultiDeviceProtocol { .map(a => a.secondaryDevicePubKey) .map(pubKey => new SecondaryPubKey(pubKey)); } + + /** + * Get all devices linked to the current user. + */ + public static async getOurDevices(): Promise> { + const ourPubKey = await UserUtil.getCurrentDevicePubKey(); + if (!ourPubKey) { + throw new Error('Public key not set.'); + } + + return this.getAllDevices(ourPubKey); + } + + /** + * Check if the given device is one of our own. + * @param device The device to check. + */ + public static async isOurDevice(device: PubKey | string): Promise { + const pubKey = typeof device === 'string' ? new PubKey(device) : device; + try { + const ourDevices = await this.getOurDevices(); + + return ourDevices.some(d => PubKey.isEqual(d, pubKey)); + } catch (e) { + return false; + } + } } From 30bc4c6cbc7458b7cf804c5fedafb197f5b680eb Mon Sep 17 00:00:00 2001 From: Mikunj Date: Wed, 17 Jun 2020 11:05:07 +1000 Subject: [PATCH 12/12] Simplify --- ts/session/protocols/MultiDeviceProtocol.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/session/protocols/MultiDeviceProtocol.ts b/ts/session/protocols/MultiDeviceProtocol.ts index 18a9a7138..602da3a1c 100644 --- a/ts/session/protocols/MultiDeviceProtocol.ts +++ b/ts/session/protocols/MultiDeviceProtocol.ts @@ -152,7 +152,7 @@ export class MultiDeviceProtocol { ] ); - return [...new Set(devices)].map(p => new PubKey(p)); + return _.uniq(devices).map(p => new PubKey(p)); } /**