diff --git a/CHANGES.md b/CHANGES.md index 6d634692f..10815e7be 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,7 +2,7 @@ ## 9.0.1 (Unreleased) -- Updated translations: lt +- Updated translations: af, ar, es, eu, fr, gl, he, lt - Increased stanza timeout from 10 to 20 seconds - Replace various font icons with SVG icons - #1761: Add a new dark theme based on the [Dracula](https://draculatheme.com/) theme diff --git a/src/headless/plugins/chat/model.js b/src/headless/plugins/chat/model.js index 102e5bb90..cfc7d6e77 100644 --- a/src/headless/plugins/chat/model.js +++ b/src/headless/plugins/chat/model.js @@ -449,8 +449,21 @@ const ChatBox = ModelWithContact.extend({ }, getUpdatedMessageAttributes (message, attrs) { - // Filter the attrs object, restricting it to only the `is_archived` key. - return (({ is_archived }) => ({ is_archived }))(attrs) + if (!attrs.error_type && message.get('error_type') === 'Decryption') { + // Looks like we have a failed decrypted message stored, and now + // we have a properly decrypted version of the same message. + // See issue: https://github.com/conversejs/converse.js/issues/2733#issuecomment-1035493594 + return Object.assign({}, attrs, { + error_condition: undefined, + error_message: undefined, + error_text: undefined, + error_type: undefined, + is_archived: attrs.is_archived, + is_ephemeral: false, + }); + } else { + return { is_archived: attrs.is_archived }; + } }, updateMessage (message, attrs) { diff --git a/src/plugins/omemo/tests/omemo.js b/src/plugins/omemo/tests/omemo.js index a3592af8b..d19b3699f 100644 --- a/src/plugins/omemo/tests/omemo.js +++ b/src/plugins/omemo/tests/omemo.js @@ -152,6 +152,75 @@ describe("The OMEMO module", function() { .toBe('Another received encrypted message without fallback'); })); + it("properly handles an already decrypted message being received again", + mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) { + + await mock.waitForRoster(_converse, 'current', 1); + const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; + await mock.initializedOMEMO(_converse); + await mock.openChatBoxFor(_converse, contact_jid); + const iq_stanza = await u.waitUntil(() => mock.deviceListFetched(_converse, contact_jid)); + let stanza = $iq({ + 'from': contact_jid, + 'id': iq_stanza.getAttribute('id'), + 'to': _converse.connection.jid, + 'type': 'result', + }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"}) + .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"}) + .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute + .c('list', {'xmlns': "eu.siacs.conversations.axolotl"}) + .c('device', {'id': '555'}); + _converse.connection._dataRecv(mock.createRequest(stanza)); + + await u.waitUntil(() => _converse.omemo_store); + + const view = _converse.chatboxviews.get(contact_jid); + view.model.set('omemo_active', true); + + // Test reception of an encrypted message + const msg_txt = 'This is an encrypted message from the contact'; + const obj = await omemo.encryptMessage(msg_txt) + const id = _converse.connection.getUniqueId(); + stanza = $msg({ + 'from': contact_jid, + 'to': _converse.connection.jid, + 'type': 'chat', + id + }).c('body').t('This is a fallback message').up() + .c('encrypted', {'xmlns': Strophe.NS.OMEMO}) + .c('header', {'sid': '555'}) + .c('key', {'rid': _converse.omemo_store.get('device_id')}) + .t(u.arrayBufferToBase64(obj.key_and_tag)).up() + .c('iv').t(obj.iv) + .up().up() + .c('payload').t(obj.payload); + _converse.connection._dataRecv(mock.createRequest(stanza)); + + // Test reception of the same message, but the decryption fails. + // The properly decrypted message should still show to the user. + // See issue: https://github.com/conversejs/converse.js/issues/2733#issuecomment-1035493594 + stanza = $msg({ + 'from': contact_jid, + 'to': _converse.connection.jid, + 'type': 'chat', + id + }).c('body').t('This is a fallback message').up() + .c('encrypted', {'xmlns': Strophe.NS.OMEMO}) + .c('header', {'sid': '555'}) + .c('key', {'rid': _converse.omemo_store.get('device_id')}) + .t(u.arrayBufferToBase64(obj.key_and_tag)).up() + .c('iv').t(obj.iv) + .up().up() + .c('payload').t(obj.payload+'x'); // Hack to break decryption. + _converse.connection._dataRecv(mock.createRequest(stanza)); + + await u.waitUntil(() => view.querySelector('.chat-msg__text')?.textContent.trim() === msg_txt); + + expect(view.model.messages.length).toBe(1); + const msg = view.model.messages.at(0); + expect(msg.get('is_ephemeral')).toBe(false) + })); + it("will create a new device based on a received carbon message", mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) {