diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index bb57284a0..3d74d43bf 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -836,225 +836,225 @@ class LokiPublicChannelAPI { params, }); - if (!res.err && res.response) { - let receivedAt = new Date().getTime(); - const pubKeys = []; - let pendingMessages = []; - pendingMessages = await Promise.all( - res.response.data.reverse().map(async adnMessage => { - // still update our last received if deleted, not signed or not valid - this.lastGot = !this.lastGot - ? adnMessage.id - : Math.max(this.lastGot, adnMessage.id); - if ( - !adnMessage.id || - !adnMessage.user || - !adnMessage.user.username || // pubKey lives in the username field - !adnMessage.text || - adnMessage.is_deleted - ) { - return false; // Invalid or delete message - } + if (res.err || !res.response) { + return; + } - const messengerData = await this.getMessengerData(adnMessage); - if (messengerData === false) { - return false; - } + let receivedAt = new Date().getTime(); + const pubKeys = []; + let pendingMessages = []; + pendingMessages = await Promise.all( + res.response.data.reverse().map(async adnMessage => { + // still update our last received if deleted, not signed or not valid + this.lastGot = !this.lastGot + ? adnMessage.id + : Math.max(this.lastGot, adnMessage.id); - const { timestamp, quote, attachments, preview } = messengerData; - if (!timestamp) { - return false; // Invalid message - } + if ( + !adnMessage.id || + !adnMessage.user || + !adnMessage.user.username || // pubKey lives in the username field + !adnMessage.text || + adnMessage.is_deleted + ) { + return false; // Invalid or delete message + } - // Duplicate check - const isDuplicate = message => { - // The username in this case is the users pubKey - const sameUsername = message.username === adnMessage.user.username; - const sameText = message.text === adnMessage.text; - // Don't filter out messages that are too far apart from each other - const timestampsSimilar = - Math.abs(message.timestamp - timestamp) <= - PUBLICCHAT_MIN_TIME_BETWEEN_DUPLICATE_MESSAGES; + const messengerData = await this.getMessengerData(adnMessage); + if (messengerData === false) { + return false; + } - return sameUsername && sameText && timestampsSimilar; - }; + const { timestamp, quote, attachments, preview } = messengerData; + if (!timestamp) { + return false; // Invalid message + } - // Filter out any messages that we got previously - if (this.lastMessagesCache.some(isDuplicate)) { - return false; // Duplicate message - } + // Duplicate check + const isDuplicate = message => { + // The username in this case is the users pubKey + const sameUsername = message.username === adnMessage.user.username; + const sameText = message.text === adnMessage.text; + // Don't filter out messages that are too far apart from each other + const timestampsSimilar = + Math.abs(message.timestamp - timestamp) <= + PUBLICCHAT_MIN_TIME_BETWEEN_DUPLICATE_MESSAGES; - // FIXME: maybe move after the de-multidev-decode - // Add the message to the lastMessage cache and keep the last 5 recent messages - this.lastMessagesCache = [ - ...this.lastMessagesCache, - { - username: adnMessage.user.username, - text: adnMessage.text, - timestamp, - }, - ].splice(-5); + return sameUsername && sameText && timestampsSimilar; + }; - const from = adnMessage.user.name || 'Anonymous'; // profileName - if (pubKeys.indexOf(`@${adnMessage.user.username}`) === -1) { - pubKeys.push(`@${adnMessage.user.username}`); - } + // Filter out any messages that we got previously + if (this.lastMessagesCache.some(isDuplicate)) { + return false; // Duplicate message + } - const messageData = { - serverId: adnMessage.id, - clientVerified: true, - friendRequest: false, - source: adnMessage.user.username, - sourceDevice: 1, + // FIXME: maybe move after the de-multidev-decode + // Add the message to the lastMessage cache and keep the last 5 recent messages + this.lastMessagesCache = [ + ...this.lastMessagesCache, + { + username: adnMessage.user.username, + text: adnMessage.text, timestamp, + }, + ].splice(-5); - serverTimestamp: timestamp, - receivedAt, - isPublic: true, - message: { - body: - adnMessage.text === timestamp.toString() ? '' : adnMessage.text, - attachments, - group: { - id: this.conversationId, - type: textsecure.protobuf.GroupContext.Type.DELIVER, - }, - flags: 0, - expireTimer: 0, - profileKey: null, - timestamp, - received_at: receivedAt, - sent_at: timestamp, - quote, - contact: [], - preview, - profile: { - displayName: from, - }, + const from = adnMessage.user.name || 'Anonymous'; // profileName + if (pubKeys.indexOf(`@${adnMessage.user.username}`) === -1) { + pubKeys.push(`@${adnMessage.user.username}`); + } + + const messageData = { + serverId: adnMessage.id, + clientVerified: true, + friendRequest: false, + source: adnMessage.user.username, + sourceDevice: 1, + timestamp, + + serverTimestamp: timestamp, + receivedAt, + isPublic: true, + message: { + body: + adnMessage.text === timestamp.toString() ? '' : adnMessage.text, + attachments, + group: { + id: this.conversationId, + type: textsecure.protobuf.GroupContext.Type.DELIVER, }, - }; - receivedAt += 1; // Ensure different arrival times + flags: 0, + expireTimer: 0, + profileKey: null, + timestamp, + received_at: receivedAt, + sent_at: timestamp, + quote, + contact: [], + preview, + profile: { + displayName: from, + }, + }, + }; + receivedAt += 1; // Ensure different arrival times - // now process any user meta data updates - // - update their conversation with a potentially new avatar - return messageData; - }) + // now process any user meta data updates + // - update their conversation with a potentially new avatar + return messageData; + }) + ); + this.conversation.setLastRetrievedMessage(this.lastGot); + + if (!pendingMessages.length) { + return; + } + if (pubKeys.length) { + // this will set slavePrimaryMap + const verifiedPrimaryPKs = await lokiFileServerAPI.verifyPrimaryPubKeys( + pubKeys ); - this.conversation.setLastRetrievedMessage(this.lastGot); + const { slavePrimaryMap } = this.serverAPI.chatAPI; - if (pendingMessages.length) { - // console.log('premultiDeviceResults', pubKeys); - if (pubKeys.length) { - // this will set slavePrimaryMap - const verifiedPrimaryPKs = await lokiFileServerAPI.verifyPrimaryPubKeys( - pubKeys - ); - const { slavePrimaryMap } = this.serverAPI.chatAPI; - - const slaveMessages = {}; - // sort pending messages - pendingMessages.forEach(messageData => { - // why am I getting these? - if (messageData === undefined) { - log.warn('invalid pendingMessages'); - return; - } - // if a known slave, queue - if (slavePrimaryMap[messageData.source]) { - // delay sending the message - if (slaveMessages[messageData.source] === undefined) { - slaveMessages[messageData.source] = [messageData]; - } else { - slaveMessages[messageData.source].push(messageData); - } - } else { - // no user or isPrimary means not multidevice, send event now - this.serverAPI.chatAPI.emit('publicMessage', { - message: messageData, - }); - } - }); - pendingMessages = []; // free memory - - // build map of userProfileName to primaryKeys - // if we have verified primaryKeys/the claimed relation - if (verifiedPrimaryPKs.length) { - // get final list of verified chat server profile names - const verifiedDeviceResults = await this.serverAPI.getUsers( - verifiedPrimaryPKs - ); - // console.log('verifiedDeviceResults', verifiedDeviceResults) - - // go through verifiedDeviceResults - const newPrimaryUserProfileName = {}; - verifiedDeviceResults.forEach(user => { - newPrimaryUserProfileName[user.username] = user.name; - }); - // replace whole - this.primaryUserProfileName = newPrimaryUserProfileName; + const slaveMessages = {}; + // sort pending messages + pendingMessages.forEach(messageData => { + // why am I getting these? + if (messageData === undefined) { + log.warn('invalid pendingMessages'); + return; + } + // if a known slave, queue + if (slavePrimaryMap[messageData.source]) { + // delay sending the message + if (slaveMessages[messageData.source] === undefined) { + slaveMessages[messageData.source] = [messageData]; + } else { + slaveMessages[messageData.source].push(messageData); } - - // process remaining messages - const ourNumber = textsecure.storage.user.getNumber(); - Object.keys(slaveMessages).forEach(slaveKey => { - // prevent our own sent messages from coming back in - if (slaveKey === ourNumber) { - // we originally sent these - return; - } - const primaryPubKey = slavePrimaryMap[slaveKey]; - slaveMessages[slaveKey].forEach(messageDataP => { - const messageData = messageDataP; // for linter - if (slavePrimaryMap[messageData.source]) { - // rewrite source, profile - messageData.source = primaryPubKey; - messageData.message.profile.displayName = this.primaryUserProfileName[ - primaryPubKey - ]; - } - this.serverAPI.chatAPI.emit('publicMessage', { - message: messageData, - }); - }); - }); - } // end if there are pending pubkeys to look up - - // console.log('pendingMessages len', pendingMessages.length); - // console.log('pendingMessages', pendingMessages); - // find messages for original slave key using slavePrimaryMap - if (pendingMessages.length) { - const { slavePrimaryMap } = this.serverAPI.chatAPI; - const ourNumber = textsecure.storage.user.getNumber(); - pendingMessages.forEach(messageDataP => { - const messageData = messageDataP; // for linter - // why am I getting these? - if (messageData === undefined) { - log.warn(`invalid pendingMessages ${pendingMessages}`); - return; - } - // prevent our own sent messages from coming back in - if (messageData.source === ourNumber) { - // we originally sent this - return; - } - if (slavePrimaryMap[messageData.source]) { - // rewrite source, profile - const primaryPubKey = slavePrimaryMap[messageData.source]; - log.info(`Rewriting ${messageData.source} to ${primaryPubKey}`); - messageData.source = primaryPubKey; - messageData.message.profile.displayName = this.primaryUserProfileName[ - primaryPubKey - ]; - } - this.serverAPI.chatAPI.emit('publicMessage', { - message: messageData, - }); + } else { + // no user or isPrimary means not multidevice, send event now + this.serverAPI.chatAPI.emit('publicMessage', { + message: messageData, }); } - pendingMessages = []; + }); + pendingMessages = []; // free memory + + // build map of userProfileName to primaryKeys + // if we have verified primaryKeys/the claimed relation + if (verifiedPrimaryPKs.length) { + // get final list of verified chat server profile names + const verifiedDeviceResults = await this.serverAPI.getUsers( + verifiedPrimaryPKs + ); + + // go through verifiedDeviceResults + const newPrimaryUserProfileName = {}; + verifiedDeviceResults.forEach(user => { + newPrimaryUserProfileName[user.username] = user.name; + }); + // replace whole + this.primaryUserProfileName = newPrimaryUserProfileName; } + + // process remaining messages + const ourNumber = textsecure.storage.user.getNumber(); + Object.keys(slaveMessages).forEach(slaveKey => { + // prevent our own sent messages from coming back in + if (slaveKey === ourNumber) { + // we originally sent these + return; + } + const primaryPubKey = slavePrimaryMap[slaveKey]; + slaveMessages[slaveKey].forEach(messageDataP => { + const messageData = messageDataP; // for linter + if (slavePrimaryMap[messageData.source]) { + // rewrite source, profile + messageData.source = primaryPubKey; + messageData.message.profile.displayName = this.primaryUserProfileName[ + primaryPubKey + ]; + } + this.serverAPI.chatAPI.emit('publicMessage', { + message: messageData, + }); + }); + }); + } // end if there are pending pubkeys to look up + + // find messages for original slave key using slavePrimaryMap + if (pendingMessages.length) { + const { slavePrimaryMap } = this.serverAPI.chatAPI; + const ourNumber = textsecure.storage.user.getNumber(); + pendingMessages.forEach(messageDataP => { + const messageData = messageDataP; // for linter + // why am I getting these? + if (messageData === undefined) { + log.warn(`invalid pendingMessages ${pendingMessages}`); + return; + } + // prevent our own sent messages from coming back in + if (messageData.source === ourNumber) { + // we originally sent this + return; + } + if (slavePrimaryMap[messageData.source]) { + // rewrite source, profile + const primaryPubKey = slavePrimaryMap[messageData.source]; + log.info(`Rewriting ${messageData.source} to ${primaryPubKey}`); + messageData.source = primaryPubKey; + messageData.message.profile.displayName = this.primaryUserProfileName[ + primaryPubKey + ]; + } + this.serverAPI.chatAPI.emit('publicMessage', { + message: messageData, + }); + }); } + pendingMessages = []; } static getPreviewFromAnnotation(annotation) { diff --git a/js/modules/loki_file_server_api.js b/js/modules/loki_file_server_api.js index 3360ba922..cd56dc9b2 100644 --- a/js/modules/loki_file_server_api.js +++ b/js/modules/loki_file_server_api.js @@ -64,7 +64,7 @@ class LokiFileServerAPI { // go through each user and find deviceMap annotations const notFoundUsers = []; - users.forEach(user => { + await Promise.all(users.map(async user => { let found = false; if (!user.annotations || !user.annotations.length) { log.info( @@ -72,67 +72,31 @@ class LokiFileServerAPI { ); return; } - user.annotations.forEach(note => { - if (note.type !== 'network.loki.messenger.devicemapping') { + const mappingNote = user.annotations.find(note => note.type === DEVICE_MAPPING_ANNOTATION_KEY); + const { authorisations } = mappingNote.value; + if (!Array.isArray(authorisations)) { + return; + } + await Promise.all(authorisations.map(async auth => { + // only skip, if in secondary search mode + if (isRequest && auth.secondaryDevicePubKey !== user.username) { + // this is not the authorization we're looking for + log.info( + `Request and ${auth.secondaryDevicePubKey} != ${user.username}` + ); return; } - // isn't desired type - // request is slave => primary type... - if ( - (isRequest && note.value.isPrimary !== '0') || - (!isRequest && note.value.isPrimary === '0') - ) { - /* log.info(`verifyUserObjectDeviceMap found wrong type of` + - `relationship ${user.username}`); */ - // console.log(`https://file.lokinet.org/users/@${user.username}?prettyPrint=1&include_annotations=1`); - return; + const valid = await libloki.crypto.validateAuthorisation(auth); + // log.info('auth is valid for', user.username) + if (iterator(user.username, auth)) { + found = true; } - const { authorisations } = note.value; - if (!Array.isArray(authorisations)) { - return; - } - authorisations.forEach(auth => { - // log.info('devmap auth', auth); - // only skip, if in secondary search mode - if (isRequest && auth.secondaryDevicePubKey !== user.username) { - // this is not the authorization we're looking for - log.info( - `Request and ${auth.secondaryDevicePubKey} != ${user.username}` - ); - return; - } - // log.info('auth', auth); - try { - // request (secondary wants to be paired with this primary) - // grant (primary approves this secondary) - window.libloki.crypto.verifyPairingSignature( - auth.primaryDevicePubKey, - auth.secondaryDevicePubKey, - dcodeIO.ByteBuffer.wrap( - isRequest ? auth.requestSignature : auth.grantSignature, - 'base64' - ).toArrayBuffer(), - isRequest - ? textsecure.protobuf.PairingAuthorisationMessage.Type.REQUEST - : textsecure.protobuf.PairingAuthorisationMessage.Type.GRANT - ); - // log.info('auth is valid for', user.username) - if (iterator(user.username, auth)) { - found = true; - } - } catch (e) { - log.warn( - `Invalid signature on pubkey ${user.username} authorization ${ - auth.secondaryDevicePubKey - } isRequest ${isRequest}` - ); - } - }); // end forEach authorisations - }); // end forEach annotations + })); // end map authorisations + if (!found) { notFoundUsers.push(user.username); } - }); // end forEach users + })); // end map users // log.info('done with users', users.length); return notFoundUsers; }