Gab Chat Bug - 200203

I have noticed a problem in encrypted Gab chat rooms with lots of messages. This happens for me in Dissenter, Safari, and Vivaldi on MacOS, but does NOT happen in Google Chrome. All the messages get decrypted, for half a second, then all but the first few are replaced by "Message failed to decrypt. The author may have invalidated or re-generated their security keys."

In the browser DevTools page, pretty-printing the decryptHtmlChatMessages function from chat.gab.com/dist/js/hydra-app.min.js yields:

        decryptHtmlChatMessages() {
            var e = this
              , t = e.chatMessageList.querySelectorAll(".message__body[data-iv]")
              , o = []
              , r = t.length
              , a = 0;
# Breakpoint here
            return t.forEach(t=>{
                if (++a,
                "true" !== t.getAttribute("data-is-processed"))
                    return t.getAttribute("data-recipient-id") !== hydra.data.user._id ? (t.parentElement.removeChild(t),
                    void e.setLoadingProgress(a / r)) : void o.push(hydra.e2e.decryptMessage(e.currentChatRoom, t).then(()=>{
                        t.setAttribute("data-is-processed", "true")
                    }
                    ).catch(()=>(t.setAttribute("data-is-processed", "true"),
                    Promise.resolve())).finally(()=>(e.setLoadingProgress(a / r),
                    Promise.resolve())));
                e.setLoadingProgress(a / r)
            }
            ),
            o
        }

decryptHtmlChatMessages gets called three times during the loading of an encrypted chat group. The first time does all the work, but the second and third times encounter "message__body" elements that are queued for decryption, but do NOT yet have their "data-is-processed" attribute set. So they attempt to decrypt the already-decrypted plaintext, and fail.

I discovered this by putting a breakpoint at "Breakpoint here", and setting t to [] on the second and third times I got there. The messages were all properly decrypted.

The fix, I believe, is to set the "data-is-processed" attribute BEFORE queueing for decryption (warning: untested code below). That ensures that the second and third passes find nothing to do:

        decryptHtmlChatMessages() {
            var e = this
              , t = e.chatMessageList.querySelectorAll(".message__body[data-iv]")
              , o = []
              , r = t.length
              , a = 0;
            return t.forEach(t=>{
                if (++a,
                "true" !== t.getAttribute("data-is-processed")) {
                  if (t.getAttribute("data-recipient-id") !== hydra.data.user._id) {
                    t.parentElement.removeChild(t);
                    void e.setLoadingProgress(a / r));
                } else {
                  t.setAttribute("data-is-processed", "true");
                  void o.push(hydra.e2e.decryptMessage(e.currentChatRoom, t)
                       .catch(()=>(t.setAttribute("data-is-processed", "true"),
                                   Promise.resolve()))
                       .finally(()=>(e.setLoadingProgress(a / r),
                                     Promise.resolve())));
                }
              e.setLoadingProgress(a / r)
            }
            ),
            o
        }

I do not know why Google Chrome is successful where the other browsers are not. Likely something about timing of the processing of enqueued promised.

=====

One other thing I noticed. This is not a bug, per se, in that it doesn't cause any incorrect output, but when an encrypted group page is loaded, it currently gets encrypted content for each group member. In groups with a handful of members this isn't too bad. For groups with 50 members, it's 50 times the data, which is an unnecessary bandwidth load on Gab's server, and may make it take noticeably longer to load the page into the user's browser.

I think it would be better to send the messages over the WebSocket connection, encrypted to only the recipient.

The HTML below is the content for a single message, taken from the Network tab of the DevTools window, pretty-printed, and left-shifted to get rid of leading white space. Note the multiple "message__body" elements inside the "message__content" element. The group I took this from has 5 members, so there are five "message__body" elements per message.

    <div class="message" data-author-id="5e35b4062a256d09a35c661f" data-author-username="found" data-message-id="5e360c9f2a256d09a35cafc4">
      <a class="message__avatar" href="https://gab.com/Found">
        <button class="avatar" title="Found">
          <img class="avatar__img" src="https://gab.com/media/user/57ea428a3546e.jpg"/>
        </button>
      </a>
      <div class="message__content">
        <div class="message__content__header">
          <span class="message__sender">
            <a class="message__sender__link" href="https://gab.com/Found">Found</a>
          </span>
          <a class="message__timestamp" data-msg-created="2020-02-01T23:41:19.375Z">11:41 PM  on  Feb 1, 2020</a>
        </div>
        <div class="message__body" data-sender-id="5e35b4062a256d09a35c661f" data-recipient-id="5e350100f778f5099e290f4f" data-iv="FZ76UJjPPn9QfIWt">WLip7m2+SqT63ErW2GDIXIuuEqb7t/6Ebe1nLMVDZJiA/s8=</div>
        <div class="message__body" data-sender-id="5e35b4062a256d09a35c661f" data-recipient-id="5e3502732a256d09a35bfc5f" data-iv="RLyPzBf97gKOtgQX">XcOreTpUi0L3jcVKnj21Otb5aiBdEDEH2zT8JnKggXirJtU=</div>
        <div class="message__body" data-sender-id="5e35b4062a256d09a35c661f" data-recipient-id="5e35ac50a3b5190604085890" data-iv="gUdnM79KhwTuYNUG">UnmsLN4QnltGz77cwhgO9G8IYXYB2jrc8E9kmaNyf6ai/mI=</div>
        <div class="message__body" data-sender-id="5e35b4062a256d09a35c661f" data-recipient-id="5e35b4062a256d09a35c661f" data-iv="E1hd+T+7urBebg/P">pvoiSSteLeCGrlORdmSD/MAiNpjtdjX1vFtfbOcYSQoZW+k=</div>
        <div class="message__body" data-sender-id="5e35b4062a256d09a35c661f" data-recipient-id="5e35e91b2a256d09a35c917f" data-iv="3xxWERVyppN8KtLg">G4HUj65c+2Vlp3Qyaanszaip1vANF8RSHYvl0DdBh7rCiEo=</div>
      </div>
      <div class="message__actions">
        <div class="message-actions">
          <div class="message-actions__container">
            <form method="POST" action="/private-message">
              <input type="hidden" name="username" value="Found"/>
              <button class="message-actions__btn" type="submit" title="Start DM with Found">
                <i class="fa fa-envelope message-actions__btn__icon"></i>
              </button>
            </form>
            <button class="message-actions__btn" title="Block this user" data-user-id="5e35b4062a256d09a35c661f" data-username="Found" data-message-id="5e360c9f2a256d09a35cafc4" onclick="return hydra.chat.blockUser(event);">
              <i class="fa fa-times-circle message-actions__btn__icon"></i>
            </button>
          </div>
        </div>
      </div>
    </div>